FEATURE: optional tool detail blocks (#662)

This is a rather huge refactor with 1 new feature (tool details can
be suppressed)

Previously we use the name "Command" to describe "Tools", this unifies
all the internal language and simplifies the code.

We also amended the persona UI to use less DToggles which aligns
with our design guidelines.

Co-authored-by: Martin Brennan <martin@discourse.org>
This commit is contained in:
Sam 2024-06-11 18:14:14 +10:00 committed by GitHub
parent 875bb04467
commit 52a7dd2a4b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
85 changed files with 495 additions and 574 deletions

View File

@ -23,7 +23,7 @@ module DiscourseAi
DiscourseAi::Configuration::LlmEnumerator.values.map do |hash|
{ id: hash[:value], name: hash[:name] }
end
render json: { ai_personas: ai_personas, meta: { commands: tools, llms: llms } }
render json: { ai_personas: ai_personas, meta: { tools: tools, llms: llms } }
end
def show
@ -126,28 +126,29 @@ module DiscourseAi
:rag_conversation_chunks,
:question_consolidator_llm,
:allow_chat,
:tool_details,
allowed_group_ids: [],
rag_uploads: [:id],
)
if commands = params.dig(:ai_persona, :commands)
permitted[:commands] = permit_commands(commands)
if tools = params.dig(:ai_persona, :tools)
permitted[:tools] = permit_tools(tools)
end
permitted
end
def permit_commands(commands)
return [] if !commands.is_a?(Array)
def permit_tools(tools)
return [] if !tools.is_a?(Array)
commands.filter_map do |command, options|
break nil if !command.is_a?(String)
tools.filter_map do |tool, options|
break nil if !tool.is_a?(String)
options&.permit! if options && options.is_a?(ActionController::Parameters)
if options
[command, options]
[tool, options]
else
command
tool
end
end
end

View File

@ -1,6 +1,9 @@
# frozen_string_literal: true
class AiPersona < ActiveRecord::Base
# TODO remove this line 01-11-2024
self.ignored_columns = [:commands]
# places a hard limit, so per site we cache a maximum of 500 classes
MAX_PERSONAS_PER_SITE = 500
@ -102,96 +105,49 @@ class AiPersona < ActiveRecord::Base
end
def class_instance
allowed_group_ids = self.allowed_group_ids
id = self.id
system = self.system
user_id = self.user_id
mentionable = self.mentionable
default_llm = self.default_llm
max_context_posts = self.max_context_posts
vision_enabled = self.vision_enabled
vision_max_pixels = self.vision_max_pixels
rag_conversation_chunks = self.rag_conversation_chunks
question_consolidator_llm = self.question_consolidator_llm
allow_chat = self.allow_chat
attributes = %i[
id
user_id
system
mentionable
default_llm
max_context_posts
vision_enabled
vision_max_pixels
rag_conversation_chunks
question_consolidator_llm
allow_chat
name
description
allowed_group_ids
tool_details
]
persona_class = DiscourseAi::AiBot::Personas::Persona.system_personas_by_id[self.id]
instance_attributes = {}
attributes.each do |attr|
value = self.read_attribute(attr)
instance_attributes[attr] = value
end
if persona_class
persona_class.define_singleton_method :allowed_group_ids do
allowed_group_ids
instance_attributes.each do |key, value|
# description/name are localized
persona_class.define_singleton_method(key) { value } if key != :description && key != :name
end
persona_class.define_singleton_method :id do
id
end
persona_class.define_singleton_method :system do
system
end
persona_class.define_singleton_method :user_id do
user_id
end
persona_class.define_singleton_method :allow_chat do
allow_chat
end
persona_class.define_singleton_method :mentionable do
mentionable
end
persona_class.define_singleton_method :default_llm do
default_llm
end
persona_class.define_singleton_method :max_context_posts do
max_context_posts
end
persona_class.define_singleton_method :vision_enabled do
vision_enabled
end
persona_class.define_singleton_method :vision_max_pixels do
vision_max_pixels
end
persona_class.define_singleton_method :question_consolidator_llm do
question_consolidator_llm
end
persona_class.define_singleton_method :rag_conversation_chunks do
rag_conversation_chunks
end
return persona_class
end
name = self.name
description = self.description
ai_persona_id = self.id
options = {}
tools = self.respond_to?(:commands) ? self.commands : self.tools
tools =
tools.filter_map do |element|
inner_name = element
current_options = nil
if element.is_a?(Array)
inner_name = element[0]
current_options = element[1]
end
# Won't migrate data yet. Let's rewrite to the tool name.
inner_name = inner_name.gsub("Command", "")
self.tools.filter_map do |element|
inner_name, current_options = element.is_a?(Array) ? element : [element, nil]
inner_name = inner_name.gsub("Tool", "")
inner_name = "List#{inner_name}" if %w[Categories Tags].include?(inner_name)
begin
klass = ("DiscourseAi::AiBot::Tools::#{inner_name}").constantize
klass = "DiscourseAi::AiBot::Tools::#{inner_name}".constantize
options[klass] = current_options if current_options
klass
rescue StandardError
@ -199,107 +155,28 @@ class AiPersona < ActiveRecord::Base
end
end
ai_persona_id = self.id
Class.new(DiscourseAi::AiBot::Personas::Persona) do
define_singleton_method :id do
id
instance_attributes.each { |key, value| define_singleton_method(key) { value } }
define_singleton_method(:to_s) do
"#<#{self.class.name} @name=#{name} @allowed_group_ids=#{allowed_group_ids.join(",")}>"
end
define_singleton_method :name do
name
end
define_singleton_method(:inspect) { to_s }
define_singleton_method :user_id do
user_id
end
define_singleton_method :description do
description
end
define_singleton_method :system do
system
end
define_singleton_method :allowed_group_ids do
allowed_group_ids
end
define_singleton_method :user_id do
user_id
end
define_singleton_method :mentionable do
mentionable
end
define_singleton_method :default_llm do
default_llm
end
define_singleton_method :max_context_posts do
max_context_posts
end
define_singleton_method :vision_enabled do
vision_enabled
end
define_singleton_method :vision_max_pixels do
vision_max_pixels
end
define_singleton_method :rag_conversation_chunks do
rag_conversation_chunks
end
define_singleton_method :question_consolidator_llm do
question_consolidator_llm
end
define_singleton_method :allow_chat do
allow_chat
end
define_singleton_method :to_s do
"#<DiscourseAi::AiBot::Personas::Persona::Custom @name=#{self.name} @allowed_group_ids=#{self.allowed_group_ids.join(",")}>"
end
define_singleton_method :inspect do
"#<DiscourseAi::AiBot::Personas::Persona::Custom @name=#{self.name} @allowed_group_ids=#{self.allowed_group_ids.join(",")}>"
end
define_method :initialize do |*args, **kwargs|
define_method(:initialize) do |*args, **kwargs|
@ai_persona = AiPersona.find_by(id: ai_persona_id)
super(*args, **kwargs)
end
define_method :persona_id do
@ai_persona&.id
end
define_method :tools do
tools
end
define_method :options do
options
end
define_method :temperature do
@ai_persona&.temperature
end
define_method :top_p do
@ai_persona&.top_p
end
define_method :system_prompt do
@ai_persona&.system_prompt || "You are a helpful bot."
end
define_method :uploads do
@ai_persona&.uploads
end
define_method(:tools) { tools }
define_method(:options) { options }
define_method(:temperature) { @ai_persona&.temperature }
define_method(:top_p) { @ai_persona&.top_p }
define_method(:system_prompt) { @ai_persona&.system_prompt || "You are a helpful bot." }
define_method(:uploads) { @ai_persona&.uploads }
end
end
@ -357,7 +234,7 @@ class AiPersona < ActiveRecord::Base
end
def system_persona_unchangeable
if top_p_changed? || temperature_changed? || system_prompt_changed? || commands_changed? ||
if top_p_changed? || temperature_changed? || system_prompt_changed? || tools_changed? ||
name_changed? || description_changed?
errors.add(:base, I18n.t("discourse_ai.ai_bot.personas.cannot_edit_system_persona"))
end
@ -378,7 +255,7 @@ end
# id :bigint not null, primary key
# name :string(100) not null
# description :string(2000) not null
# commands :json not null
# tools :json not null
# system_prompt :string(10000000) not null
# allowed_group_ids :integer default([]), not null, is an Array
# created_by_id :integer
@ -408,6 +285,7 @@ end
# role_max_responses_per_hour :integer default(50), not null
# question_consolidator_llm :text
# allow_chat :boolean default(FALSE), not null
# tool_details :boolean default(TRUE), not null
#
# Indexes
#

View File

@ -9,7 +9,7 @@ class LocalizedAiPersonaSerializer < ApplicationSerializer
:enabled,
:system,
:priority,
:commands,
:tools,
:system_prompt,
:allowed_group_ids,
:temperature,
@ -24,7 +24,8 @@ class LocalizedAiPersonaSerializer < ApplicationSerializer
:rag_chunk_overlap_tokens,
:rag_conversation_chunks,
:question_consolidator_llm,
:allow_chat
:allow_chat,
:tool_details
has_one :user, serializer: BasicUserSerializer, embed: :object
has_many :rag_uploads, serializer: UploadSerializer, embed: :object

View File

@ -6,7 +6,7 @@ const CREATE_ATTRIBUTES = [
"id",
"name",
"description",
"commands",
"tools",
"system_prompt",
"allowed_group_ids",
"enabled",
@ -27,6 +27,7 @@ const CREATE_ATTRIBUTES = [
"rag_conversation_chunks",
"question_consolidator_llm",
"allow_chat",
"tool_details",
];
const SYSTEM_ATTRIBUTES = [
@ -48,37 +49,37 @@ const SYSTEM_ATTRIBUTES = [
"rag_conversation_chunks",
"question_consolidator_llm",
"allow_chat",
"tool_details",
];
class CommandOption {
class ToolOption {
@tracked value = null;
}
export default class AiPersona extends RestModel {
// this code is here to convert the wire schema to easier to work with object
// on the wire we pass in/out commands as an Array.
// [[CommandName, {option1: value, option2: value}], CommandName2, CommandName3]
// So we rework this into a "commands" property and nested commandOptions
// on the wire we pass in/out tools as an Array.
// [[ToolName, {option1: value, option2: value}], ToolName2, ToolName3]
// So we rework this into a "tools" property and nested toolOptions
init(properties) {
if (properties.commands) {
properties.commands = properties.commands.map((command) => {
if (typeof command === "string") {
return command;
if (properties.tools) {
properties.tools = properties.tools.map((tool) => {
if (typeof tool === "string") {
return tool;
} else {
let [commandId, options] = command;
let [toolId, options] = tool;
for (let optionId in options) {
if (!options.hasOwnProperty(optionId)) {
continue;
}
this.getCommandOption(commandId, optionId).value =
options[optionId];
this.getToolOption(toolId, optionId).value = options[optionId];
}
return commandId;
return toolId;
}
});
}
super.init(properties);
this.commands = properties.commands;
this.tools = properties.tools;
}
async createUser() {
@ -93,23 +94,23 @@ export default class AiPersona extends RestModel {
return this.user;
}
getCommandOption(commandId, optionId) {
this.commandOptions ||= {};
this.commandOptions[commandId] ||= {};
return (this.commandOptions[commandId][optionId] ||= new CommandOption());
getToolOption(toolId, optionId) {
this.toolOptions ||= {};
this.toolOptions[toolId] ||= {};
return (this.toolOptions[toolId][optionId] ||= new ToolOption());
}
populateCommandOptions(attrs) {
if (!attrs.commands) {
populateToolOptions(attrs) {
if (!attrs.tools) {
return;
}
let commandsWithOptions = [];
attrs.commands.forEach((commandId) => {
if (typeof commandId !== "string") {
commandId = commandId[0];
let toolsWithOptions = [];
attrs.tools.forEach((toolId) => {
if (typeof toolId !== "string") {
toolId = toolId[0];
}
if (this.commandOptions && this.commandOptions[commandId]) {
let options = this.commandOptions[commandId];
if (this.toolOptions && this.toolOptions[toolId]) {
let options = this.toolOptions[toolId];
let optionsWithValues = {};
for (let optionId in options) {
if (!options.hasOwnProperty(optionId)) {
@ -118,12 +119,12 @@ export default class AiPersona extends RestModel {
let option = options[optionId];
optionsWithValues[optionId] = option.value;
}
commandsWithOptions.push([commandId, optionsWithValues]);
toolsWithOptions.push([toolId, optionsWithValues]);
} else {
commandsWithOptions.push(commandId);
toolsWithOptions.push(toolId);
}
});
attrs.commands = commandsWithOptions;
attrs.tools = toolsWithOptions;
}
updateProperties() {
@ -131,20 +132,20 @@ export default class AiPersona extends RestModel {
? this.getProperties(SYSTEM_ATTRIBUTES)
: this.getProperties(CREATE_ATTRIBUTES);
attrs.id = this.id;
this.populateCommandOptions(attrs);
this.populateToolOptions(attrs);
return attrs;
}
createProperties() {
let attrs = this.getProperties(CREATE_ATTRIBUTES);
this.populateCommandOptions(attrs);
this.populateToolOptions(attrs);
return attrs;
}
workingCopy() {
let attrs = this.getProperties(CREATE_ATTRIBUTES);
this.populateCommandOptions(attrs);
this.populateToolOptions(attrs);
return AiPersona.create(attrs);
}
}

View File

@ -1,81 +0,0 @@
import Component from "@glimmer/component";
import I18n from "discourse-i18n";
import AiPersonaCommandOptionEditor from "./ai-persona-command-option-editor";
export default class AiPersonaCommandOptions extends Component {
get showCommandOptions() {
const allCommands = this.args.allCommands;
if (!allCommands) {
return false;
}
return this.commandNames.any(
(command) => allCommands.find((c) => c.id === command)?.options
);
}
get commandNames() {
if (!this.args.commands) {
return [];
}
return this.args.commands.map((command) => {
if (typeof command === "string") {
return command;
} else {
return command[0];
}
});
}
get commandOptions() {
if (!this.args.commands) {
return [];
}
const allCommands = this.args.allCommands;
if (!allCommands) {
return [];
}
const options = [];
this.commandNames.forEach((commandId) => {
const command = allCommands.find((c) => c.id === commandId);
const commandName = command?.name;
const commandOptions = command?.options;
if (commandOptions) {
const mappedOptions = Object.keys(commandOptions).map((key) => {
const value = this.args.persona.getCommandOption(commandId, key);
return Object.assign({}, commandOptions[key], { id: key, value });
});
options.push({ commandName, options: mappedOptions });
}
});
return options;
}
<template>
{{#if this.showCommandOptions}}
<div class="control-group">
<label>{{I18n.t "discourse_ai.ai_persona.command_options"}}</label>
<div>
{{#each this.commandOptions as |commandOption|}}
<div class="ai-persona-editor__command-options">
<div class="ai-persona-editor__command-options-name">
{{commandOption.commandName}}
</div>
<div class="ai-persona-editor__command-option-options">
{{#each commandOption.options as |option|}}
<AiPersonaCommandOptionEditor @option={{option}} />
{{/each}}
</div>
</div>
{{/each}}
</div>
</div>
{{/if}}
</template>
}

View File

@ -20,9 +20,9 @@ import AdminUser from "admin/models/admin-user";
import ComboBox from "select-kit/components/combo-box";
import GroupChooser from "select-kit/components/group-chooser";
import DTooltip from "float-kit/components/d-tooltip";
import AiCommandSelector from "./ai-command-selector";
import AiLlmSelector from "./ai-llm-selector";
import AiPersonaCommandOptions from "./ai-persona-command-options";
import AiPersonaToolOptions from "./ai-persona-tool-options";
import AiToolSelector from "./ai-tool-selector";
import PersonaRagUploader from "./persona-rag-uploader";
export default class PersonaEditor extends Component {
@ -201,21 +201,6 @@ export default class PersonaEditor extends Component {
await this.toggleField("priority", true);
}
@action
async toggleMentionable() {
await this.toggleField("mentionable");
}
@action
async toggleAllowChat() {
await this.toggleField("allow_chat");
}
@action
async toggleVisionEnabled() {
await this.toggleField("vision_enabled");
}
@action
async createUser() {
try {
@ -303,45 +288,6 @@ export default class PersonaEditor extends Component {
@content={{I18n.t "discourse_ai.ai_persona.priority_help"}}
/>
</div>
{{#if this.editingModel.user}}
{{#if this.chatPluginEnabled}}
<div class="control-group ai-persona-editor__allow_chat">
<DToggleSwitch
class="ai-persona-editor__allow_chat_toggle"
@state={{@model.allow_chat}}
@label="discourse_ai.ai_persona.allow_chat"
{{on "click" this.toggleAllowChat}}
/>
<DTooltip
@icon="question-circle"
@content={{I18n.t "discourse_ai.ai_persona.allow_chat_help"}}
/>
</div>
{{/if}}
<div class="control-group ai-persona-editor__mentionable">
<DToggleSwitch
class="ai-persona-editor__mentionable_toggle"
@state={{@model.mentionable}}
@label="discourse_ai.ai_persona.mentionable"
{{on "click" this.toggleMentionable}}
/>
<DTooltip
@icon="question-circle"
@content={{I18n.t "discourse_ai.ai_persona.mentionable_help"}}
/>
</div>
{{/if}}
<div class="control-group ai-persona-editor__vision_enabled">
<DToggleSwitch
@state={{@model.vision_enabled}}
@label="discourse_ai.ai_persona.vision_enabled"
{{on "click" this.toggleVisionEnabled}}
/>
<DTooltip
@icon="question-circle"
@content={{I18n.t "discourse_ai.ai_persona.vision_enabled_help"}}
/>
</div>
<div class="control-group">
<label>{{I18n.t "discourse_ai.ai_persona.name"}}</label>
<Input
@ -400,19 +346,19 @@ export default class PersonaEditor extends Component {
</div>
{{/unless}}
<div class="control-group">
<label>{{I18n.t "discourse_ai.ai_persona.commands"}}</label>
<AiCommandSelector
class="ai-persona-editor__commands"
@value={{this.editingModel.commands}}
<label>{{I18n.t "discourse_ai.ai_persona.tools"}}</label>
<AiToolSelector
class="ai-persona-editor__tools"
@value={{this.editingModel.tools}}
@disabled={{this.editingModel.system}}
@commands={{@personas.resultSetMeta.commands}}
@tools={{@personas.resultSetMeta.tools}}
/>
</div>
{{#unless this.editingModel.system}}
<AiPersonaCommandOptions
<AiPersonaToolOptions
@persona={{this.editingModel}}
@commands={{this.editingModel.commands}}
@allCommands={{@personas.resultSetMeta.commands}}
@tools={{this.editingModel.tools}}
@allTools={{@personas.resultSetMeta.tools}}
/>
{{/unless}}
<div class="control-group">
@ -433,6 +379,65 @@ export default class PersonaEditor extends Component {
disabled={{this.editingModel.system}}
/>
</div>
{{#if this.editingModel.user}}
{{#if this.chatPluginEnabled}}
<div class="control-group ai-persona-editor__allow_chat">
<label>
<Input
@type="checkbox"
@checked={{this.editingModel.allow_chat}}
/>
{{I18n.t "discourse_ai.ai_persona.allow_chat"}}</label>
<DTooltip
@icon="question-circle"
@content={{I18n.t "discourse_ai.ai_persona.allow_chat_help"}}
/>
</div>
{{/if}}
<div class="control-group ai-persona-editor__mentionable">
<label>
<Input
@type="checkbox"
@checked={{this.editingModel.mentionable}}
/>
{{I18n.t "discourse_ai.ai_persona.mentionable"}}</label>
<DTooltip
@icon="question-circle"
@content={{I18n.t "discourse_ai.ai_persona.mentionable_help"}}
/>
</div>
{{/if}}
<div class="control-group ai-persona-editor__tool-details">
<label>
<Input @type="checkbox" @checked={{this.editingModel.tool_details}} />
{{I18n.t "discourse_ai.ai_persona.tool_details"}}</label>
<DTooltip
@icon="question-circle"
@content={{I18n.t "discourse_ai.ai_persona.tool_details_help"}}
/>
</div>
<div class="control-group ai-persona-editor__vision_enabled">
<label>
<Input
@type="checkbox"
@checked={{this.editingModel.vision_enabled}}
/>
{{I18n.t "discourse_ai.ai_persona.vision_enabled"}}</label>
<DTooltip
@icon="question-circle"
@content={{I18n.t "discourse_ai.ai_persona.vision_enabled_help"}}
/>
</div>
{{#if this.editingModel.vision_enabled}}
<div class="control-group">
<label>{{I18n.t "discourse_ai.ai_persona.vision_max_pixels"}}</label>
<ComboBox
@value={{this.maxPixelsValue}}
@content={{this.maxPixelValues}}
@onChange={{this.onChangeMaxPixels}}
/>
</div>
{{/if}}
<div class="control-group">
<label>{{I18n.t "discourse_ai.ai_persona.max_context_posts"}}</label>
<Input
@ -446,18 +451,8 @@ export default class PersonaEditor extends Component {
@content={{I18n.t "discourse_ai.ai_persona.max_context_posts_help"}}
/>
</div>
{{#if @model.vision_enabled}}
<div class="control-group">
<label>{{I18n.t "discourse_ai.ai_persona.vision_max_pixels"}}</label>
<ComboBox
@value={{this.maxPixelsValue}}
@content={{this.maxPixelValues}}
@onChange={{this.onChangeMaxPixels}}
/>
</div>
{{/if}}
<div class="control-group">
{{#if this.showTemperature}}
<div class="control-group">
<label>{{I18n.t "discourse_ai.ai_persona.temperature"}}</label>
<Input
@type="number"
@ -471,8 +466,10 @@ export default class PersonaEditor extends Component {
@icon="question-circle"
@content={{I18n.t "discourse_ai.ai_persona.temperature_help"}}
/>
</div>
{{/if}}
{{#if this.showTopP}}
<div class="control-group">
<label>{{I18n.t "discourse_ai.ai_persona.top_p"}}</label>
<Input
@type="number"
@ -486,8 +483,8 @@ export default class PersonaEditor extends Component {
@icon="question-circle"
@content={{I18n.t "discourse_ai.ai_persona.top_p_help"}}
/>
{{/if}}
</div>
{{/if}}
{{#if this.siteSettings.ai_embeddings_enabled}}
<div class="control-group">
<PersonaRagUploader

View File

@ -3,7 +3,7 @@ import { Input } from "@ember/component";
import { on } from "@ember/modifier";
import { action } from "@ember/object";
export default class AiPersonaCommandOptionEditor extends Component {
export default class AiPersonaToolOptionEditor extends Component {
get isBoolean() {
return this.args.option.type === "boolean";
}
@ -18,7 +18,7 @@ export default class AiPersonaCommandOptionEditor extends Component {
}
<template>
<div class="control-group ai-persona-command-option-editor">
<div class="control-group ai-persona-tool-option-editor">
<label>
{{@option.name}}
</label>
@ -35,7 +35,7 @@ export default class AiPersonaCommandOptionEditor extends Component {
{{/if}}
</div>
{{#unless this.isBoolean}}
<div class="ai-persona-command-option-editor__instructions">
<div class="ai-persona-tool-option-editor__instructions">
{{@option.description}}
</div>
{{/unless}}

View File

@ -0,0 +1,79 @@
import Component from "@glimmer/component";
import I18n from "discourse-i18n";
import AiPersonaToolOptionEditor from "./ai-persona-tool-option-editor";
export default class AiPersonaToolOptions extends Component {
get showToolOptions() {
const allTools = this.args.allTools;
if (!allTools) {
return false;
}
return this.toolNames.any((tool) => allTools.findBy("id", tool)?.options);
}
get toolNames() {
if (!this.args.tools) {
return [];
}
return this.args.tools.map((tool) => {
if (typeof tool === "string") {
return tool;
} else {
return tool[0];
}
});
}
get toolOptions() {
if (!this.args.tools) {
return [];
}
const allTools = this.args.allTools;
if (!allTools) {
return [];
}
const options = [];
this.toolNames.forEach((toolId) => {
const tool = allTools.findBy("id", toolId);
const toolName = tool?.name;
const toolOptions = tool?.options;
if (toolOptions) {
const mappedOptions = Object.keys(toolOptions).map((key) => {
const value = this.args.persona.getToolOption(toolId, key);
return Object.assign({}, toolOptions[key], { id: key, value });
});
options.push({ toolName, options: mappedOptions });
}
});
return options;
}
<template>
{{#if this.showToolOptions}}
<div class="control-group">
<label>{{I18n.t "discourse_ai.ai_persona.tool_options"}}</label>
<div>
{{#each this.toolOptions as |toolOption|}}
<div class="ai-persona-editor__tool-options">
<div class="ai-persona-editor__tool-options-name">
{{toolOption.toolName}}
</div>
<div class="ai-persona-editor__tool-option-options">
{{#each toolOption.options as |option|}}
<AiPersonaToolOptionEditor @option={{option}} />
{{/each}}
</div>
</div>
{{/each}}
</div>
</div>
{{/if}}
</template>
}

View File

@ -7,7 +7,7 @@ export default MultiSelectComponent.extend({
}),
content: computed(function () {
return this.commands;
return this.tools;
}),
value: "",

View File

@ -23,7 +23,7 @@
}
}
.ai-persona-command-option-editor {
.ai-persona-tool-option-editor {
&__instructions {
color: var(--primary-medium);
font-size: var(--font-down-1);
@ -49,12 +49,12 @@
label {
display: block;
}
&__command-options {
&__tool-options {
padding: 5px 10px 5px;
border: 1px solid var(--primary-low-mid);
width: 480px;
}
&__command-options-name {
&__tool-options-name {
margin-bottom: 10px;
font-size: var(--font-down-1);
}
@ -65,25 +65,16 @@
width: 500px;
height: 400px;
}
&__priority {
display: flex;
align-items: center;
}
&__tool-details,
&__vision_enabled,
&__allow_chat,
&__priority,
&__mentionable {
display: flex;
align-items: center;
}
&__allow_chat {
display: flex;
align-items: center;
}
&__vision_enabled {
display: flex;
align-items: center;
}
&__indexing-options {
display: block;
margin-top: 1em;

View File

@ -111,7 +111,7 @@ ar:
delete: حذف
priority: الأولوية
priority_help: يتم عرض الشخصيات ذات الأولوية للمستخدمين في أعلى قائمة الشخصيات. إذا كانت الأولوية لعدة أشخاص، فسيتم فرزهم أبجديًا.
command_options: "خيارات الأوامر"
tool_options: "خيارات الأوامر"
uploads:
title: "التحميلات"
uploading: "جارٍ التحميل..."

View File

@ -169,7 +169,7 @@ de:
top_p_help: Top P für die LLM, erhöhen, um die Zufälligkeit zu erhöhen (leer lassen, um die Modellvorgabe zu verwenden, in der Regel ein Wert zwischen 0,0 und 1,0)
priority: Priorität
priority_help: Personas mit Priorität werden den Benutzern am Anfang der Persona-Liste angezeigt. Wenn mehrere Personas Priorität haben, werden sie alphabetisch sortiert.
command_options: "Befehlsoptionen"
tool_options: "Befehlsoptionen"
rag_chunk_tokens: "Chunk-Token hochladen"
rag_chunk_tokens_help: "Die Anzahl der Token, die für jeden Chunk im RAG-Modell verwendet werden. Erhöhen, um die Menge des Kontexts zu erhöhen, den die KI verwenden kann. (Eine Änderung führt zu einer Neuindizierung aller Uploads)"
rag_chunk_overlap_tokens: "Chunk-Überlappungs-Token hochladen"

View File

@ -131,12 +131,14 @@ en:
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)"
vision_enabled: Vision Enabled
vision_enabled_help: If enabled, the AI will attempt to understand images users post in the topic, depends on the model being used supporting vision. Anthropic Claude 3 models support vision.
vision_enabled_help: If enabled, the AI will attempt to understand images users post in the topic, depends on the model being used supporting vision. Supported by latest models from Anthropic, Google, and OpenAI.
vision_max_pixels: Supported image size
vision_max_pixel_sizes:
low: Low Quality - cheapest (256x256)
medium: Medium Quality (512x512)
high: High Quality - slowest (1024x1024)
tool_details: Show Tool Details
tool_details_help: Will show end users details on which tools the language model has triggered.
mentionable: Allow Mentions
mentionable_help: If enabled, users in allowed groups can mention this user in posts, the AI will respond as this persona.
user: User
@ -154,7 +156,7 @@ en:
save: Save
saved: AI Persona Saved
enabled: "Enabled?"
commands: Enabled Commands
tools: Enabled Tools
allowed_groups: Allowed Groups
confirm_delete: Are you sure you want to delete this persona?
new: "New Persona"
@ -167,7 +169,7 @@ en:
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_help: Priority personas are displayed to users at the top of the persona list. If multiple personas have priority, they will be sorted alphabetically.
command_options: "Command Options"
tool_options: "Tool Options"
rag_chunk_tokens: "Upload Chunk Tokens"
rag_chunk_tokens_help: "The number of tokens to use for each chunk in the RAG model. Increase to increase the amount of context the AI can use. (changing will re-index all uploads)"
rag_chunk_overlap_tokens: "Upload Chunk Overlap Tokens"

View File

@ -111,7 +111,7 @@ es:
delete: Eliminar
priority: Prioridad
priority_help: Las personas prioritarias se muestran a los usuarios en la parte superior de la lista de personas. Si varias personas tienen prioridad, se ordenarán alfabéticamente.
command_options: "Opciones de comando"
tool_options: "Opciones de comando"
uploads:
title: "Subidos"
uploading: "Subiendo..."

View File

@ -111,7 +111,7 @@ fi:
delete: Poista
priority: Prioriteetti
priority_help: Prioriteettipersoonat näytetään käyttäjille ensimmäisinä persoonaluettelossa. Jos useilla persoonilla on prioriteetti, ne järjestetään aakkosjärjestyksessä.
command_options: "Komentoasetukset"
tool_options: "Komentoasetukset"
uploads:
title: "Lataukset"
uploading: "Ladataan..."

View File

@ -111,7 +111,7 @@ fr:
delete: Supprimer
priority: Priorité
priority_help: Les personnages prioritaires sont affichés aux utilisateurs en haut de la liste des personnages. Si plusieurs personnages sont prioritaires, ils seront triés par ordre alphabétique.
command_options: "Options de commande"
tool_options: "Options de commande"
uploads:
title: "Fichiers envoyés"
uploading: "Envoi en cours…"

View File

@ -169,7 +169,7 @@ he:
top_p_help: ה־P המובילים לשימוש למודל השפה הגדול (LLM), הגדלה תגדיל את היצירתיות (אפשר להשאיר ריק לשימוש בברירת המחדל של הדגם, בדרך כלל זה ערך בין 0.0 לבין 1.0)
priority: עדיפות
priority_help: דמויות בעדיפות גבוהה מוצגות למשתמשים בראש רשימת הדמויות. אם מספר דמויות הן בעדיפות הן תסודרנה לפי האלפבית.
command_options: "אפשרויות פקודה"
tool_options: "אפשרויות פקודה"
rag_chunk_tokens: "העלאת אסימוני חלקים"
rag_chunk_tokens_help: "מספר האסימונים לשימוש לכל נתח במודל ה־RAG. הגדלה תגדיל את כמות ההקשר בו יכולה להשתמש הבינה המלאכותית. (שינוי יסדר את כל ההעלאות במפתח מחדש)"
rag_chunk_overlap_tokens: "העלאת אסימוני חפיפת חלקים"

View File

@ -111,7 +111,7 @@ it:
delete: Elimina
priority: Priorità
priority_help: I personaggi prioritari vengono visualizzati agli utenti nella parte superiore dell'elenco dei personaggi. Se più personaggi hanno la priorità, verranno ordinati in ordine alfabetico.
command_options: "Opzioni di comando"
tool_options: "Opzioni di comando"
uploads:
title: "Caricamenti"
uploading: "Caricamento..."

View File

@ -111,7 +111,7 @@ ja:
delete: 削除
priority: 優先度
priority_help: 優先ペルソナはペルソナリストの先頭に表示されます。複数のペルソナが優先されている場合は、アルファベット順に並べ替えられます。
command_options: "コマンドオプション"
tool_options: "コマンドオプション"
uploads:
title: "アップロード"
uploading: "アップロード中..."

View File

@ -111,7 +111,7 @@ nl:
delete: Verwijderen
priority: Prioriteit
priority_help: Prioritaire persona's worden bovenaan de personalijst weergegeven voor gebruikers. Als meerdere persona's prioriteit hebben, worden deze alfabetisch gesorteerd.
command_options: "Opdrachtopties"
tool_options: "Opdrachtopties"
uploads:
title: "Uploads"
uploading: "Uploaden..."

View File

@ -137,7 +137,7 @@ pl_PL:
temperature_help: Temperatura do zastosowania w LLM, zwiększ, aby zwiększyć kreatywność (pozostaw puste, aby użyć domyślnego modelu, zazwyczaj wartość od 0,0 do 2,0)
priority: Priorytet
priority_help: Priorytetowe persony są wyświetlane użytkownikom na górze listy person. Jeśli wiele person ma priorytet, zostaną one posortowane alfabetycznie.
command_options: "Opcje poleceń"
tool_options: "Opcje poleceń"
uploads:
title: "Pliki"
button: "Dodaj pliki"

View File

@ -112,7 +112,7 @@ pt_BR:
delete: Excluir
priority: Prioridade
priority_help: Personas de prioridade são exibidas aos(às) usuários(as) no topo da lista de personas. Se várias personas tiverem prioridade, serão escolhidas em ordem alfabética.
command_options: "Opções de comando"
tool_options: "Opções de comando"
uploads:
title: "Envios"
uploading: "Enviando..."

View File

@ -111,7 +111,7 @@ ru:
delete: Удалить
priority: Приоритет
priority_help: Приоритетные персоны показываются пользователям вверху списка персон. Если приоритет имеют несколько персон, они будут отсортированы в алфавитном порядке.
command_options: "Параметры команды"
tool_options: "Параметры команды"
uploads:
title: "Загрузки"
uploading: "Загрузка…"

View File

@ -153,7 +153,7 @@ tr_TR:
top_p_help: LLM için kullanılacak en yüksek P, rastgeleliği artırmak için artırın (model varsayılanını kullanmak için boş bırakın, genellikle 0.0 ila 1.0 arasında bir değer)
priority: Öncelik
priority_help: Öncelikli kişilikler kullanıcılara kişilik listesinin en üstünde gösterilir. Birden fazla kişiliğin önceliği varsa bunlar alfabetik olarak sıralanır.
command_options: "Komut Seçenekleri"
tool_options: "Komut Seçenekleri"
what_are_personas: "Yapay Zeka Personaları nedir?"
no_persona_selected: |
YZ Kişilikleri, Discourse forumunuzda YZ motorunun davranışını özelleştirebilmenizi sağlayan güçlü bir özelliktir. YZ'nin yanıt ve etkileşimlerine rehberlik eden bir "sistem mesajı" görevi görerek daha kişiselleştirilmiş ve etkileşimli bir kullanıcı deneyimi oluşturmaya yardımcı olurlar.

View File

@ -128,7 +128,7 @@ zh_CN:
top_p_help: 用于 LLM 的 Top P增大它可提升创造力留空以使用模型默认值通常为 0.0 到 1.0 之间的值)
priority: 优先
priority_help: 优先角色会在角色列表的顶部向用户显示。如果多个角色都具有优先级,将按字母顺序排序。
command_options: "命令选项"
tool_options: "命令选项"
uploads:
title: "上传"
uploading: "正在上传…"

View File

@ -151,7 +151,7 @@ ar:
topic_not_found: "الملخص غير متوفر، الموضوع غير موجود!"
summarizing: "جارٍ تلخيص الموضوع"
searching: "جارٍ البحث عن: '%{query}'"
command_options:
tool_options:
search:
max_results:
name: "الحد الأقصى لعدد النتائج"
@ -159,7 +159,7 @@ ar:
base_query:
name: "استعلام البحث الأساسي"
description: "استعلام البحث الأساسي الذي سيتم استخدامه عند البحث. على سبيل المثال: سيسبق \"#urgent\" \"#urgent\" في استعلام البحث وسيتضمن الموضوعات ذات الفئة أو الوسم العاجل فقط."
command_summary:
tool_summary:
categories: "إدراج الفئات"
search: "البحث"
tags: "إدراج الوسوم"
@ -172,7 +172,7 @@ ar:
schema: "البحث عن مخطط قاعدة البيانات"
search_settings: "جارٍ البحث في إعدادات الموقع"
dall_e: "إنشاء صورة"
command_help:
tool_help:
categories: "إدراج جميع الفئات المرئية بشكلٍ عام في المنتدى"
search: "البحث في جميع الموضوعات العامة في المنتدى"
tags: "إدراج جميع الوسوم في المنتدى"
@ -185,7 +185,7 @@ ar:
schema: "البحث عن مخطط قاعدة البيانات"
search_settings: "البحث في إعدادات الموقع"
dall_e: "إنشاء صورة باستخدام DALL-E 3"
command_description:
tool_description:
read: "القراءة: <a href='%{url}'>%{title}</a>"
time: "الوقت في المنطقة الزمنية %{timezone} هو %{time}"
summarize: "تم تلخيص <a href='%{url}'>%{title}</a>"

View File

@ -7,7 +7,7 @@
be:
discourse_ai:
ai_bot:
command_summary:
tool_summary:
search: "Пошук"
sentiment:
reports:

View File

@ -10,7 +10,7 @@ bg:
yaxis: "Дата"
discourse_ai:
ai_bot:
command_summary:
tool_summary:
search: "Търсене"
time: "Време "
summarize: "Обобщаване"

View File

@ -10,6 +10,6 @@ bs_BA:
yaxis: "Datum"
discourse_ai:
ai_bot:
command_summary:
tool_summary:
search: "Pretraži"
time: "Vrijeme"

View File

@ -10,7 +10,7 @@ ca:
yaxis: "Data"
discourse_ai:
ai_bot:
command_summary:
tool_summary:
search: "Cerca"
time: "Hora"
sentiment:

View File

@ -10,7 +10,7 @@ cs:
yaxis: "Datum"
discourse_ai:
ai_bot:
command_summary:
tool_summary:
search: "Vyhledat"
tags: "Seznam značek"
time: "Čas"

View File

@ -10,7 +10,7 @@ da:
yaxis: "Dato"
discourse_ai:
ai_bot:
command_summary:
tool_summary:
search: "Søg"
time: "Tidspunkt"
sentiment:

View File

@ -193,7 +193,7 @@ de:
topic_not_found: "Zusammenfassung nicht verfügbar, Thema nicht gefunden!"
summarizing: "Thema zusammenfassen"
searching: "Suche nach: „%{query}“"
command_options:
tool_options:
search:
search_private:
name: "Suche Privat"
@ -204,7 +204,7 @@ de:
base_query:
name: "Basissuchanfrage"
description: "Basisanfrage, die bei der Suche verwendet wird. Beispiel: Bei „#dringend“ wird der Suchanfrage „#dringend“ vorangestellt und es werden nur Themen mit der Kategorie oder dem Schlagwort „dringend“ angezeigt."
command_summary:
tool_summary:
web_browser: "Web durchsuchen"
github_search_files: "GitHub Datei-Suche"
github_search_code: "GitHub Code-Suche"
@ -225,7 +225,7 @@ de:
dall_e: "Bild generieren"
search_meta_discourse: "Suche Meta Discourse"
javascript_evaluator: "JavaScript auswerten"
command_help:
tool_help:
web_browser: "Webseite mit dem KI Bot durchsuchen"
github_search_code: "Suche nach Code in einem GitHub-Repository"
github_search_files: "Suche nach Dateien in einem GitHub-Repository"
@ -246,7 +246,7 @@ de:
dall_e: "Bild mit DALL-E 3 generieren"
search_meta_discourse: "Suche Meta Discourse"
javascript_evaluator: "JavaScript auswerten"
command_description:
tool_description:
web_browser: "Lesen <a href='%{url}'>%{url}</a>"
github_search_files: "Gesucht wurde nach '%{keywords}' in %{repo}/%{branch}"
github_search_code: "Gesucht wurde nach '%{query}' in %{repo}"

View File

@ -10,6 +10,6 @@ el:
yaxis: "Ημερομηνία"
discourse_ai:
ai_bot:
command_summary:
tool_summary:
search: "Αναζήτηση"
time: "Ώρα"

View File

@ -175,7 +175,7 @@ en:
personas:
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_edit_system_persona: "System personas can only be renamed, you may not edit commands or system prompt, instead disable and make a copy"
cannot_edit_system_persona: "System personas can only be renamed, you may not edit tools or system prompt, instead disable and make a copy"
github_helper:
name: "GitHub Helper"
description: "AI Bot specialized in assisting with GitHub-related tasks and questions"
@ -206,7 +206,7 @@ en:
topic_not_found: "Summary unavailable, topic not found!"
summarizing: "Summarizing topic"
searching: "Searching for: '%{query}'"
command_options:
tool_options:
search:
search_private:
name: "Search Private"
@ -217,7 +217,7 @@ en:
base_query:
name: "Base Search Query"
description: "Base query to use when searching. Example: '#urgent' will prepend '#urgent' to the search query and only include topics with the urgent category or tag."
command_summary:
tool_summary:
web_browser: "Browse Web"
github_search_files: "GitHub search files"
github_search_code: "GitHub code search"
@ -238,7 +238,7 @@ en:
dall_e: "Generate image"
search_meta_discourse: "Search Meta Discourse"
javascript_evaluator: "Evaluate JavaScript"
command_help:
tool_help:
web_browser: "Browse web page using the AI Bot"
github_search_code: "Search for code in a GitHub repository"
github_search_files: "Search for files in a GitHub repository"
@ -259,7 +259,7 @@ en:
dall_e: "Generate image using DALL-E 3"
search_meta_discourse: "Search Meta Discourse"
javascript_evaluator: "Evaluate JavaScript"
command_description:
tool_description:
web_browser: "Reading <a href='%{url}'>%{url}</a>"
github_search_files: "Searched for '%{keywords}' in %{repo}/%{branch}"
github_search_code: "Searched for '%{query}' in %{repo}"

View File

@ -151,7 +151,7 @@ es:
topic_not_found: "¡Resumen no disponible, tema no encontrado!"
summarizing: "Resumiendo tema"
searching: "Buscando: '%{query}'"
command_options:
tool_options:
search:
max_results:
name: "Número máximo de resultados"
@ -159,7 +159,7 @@ es:
base_query:
name: "Consulta de búsqueda básica"
description: "Consulta base a utilizar en la búsqueda. Ejemplo: «#urgente» antepondrá «#urgente» a la consulta de búsqueda y solo incluirá temas con la categoría o etiqueta urgente."
command_summary:
tool_summary:
categories: "Lista de categorías"
search: "Buscar"
tags: "Listar etiquetas"
@ -172,7 +172,7 @@ es:
schema: "Buscar esquema de base de datos"
search_settings: "Buscando los ajustes del sitio"
dall_e: "Generar imagen"
command_help:
tool_help:
categories: "Listar todas las categorías visibles públicamente en el foro"
search: "Buscar todos los temas públicos en el foro."
tags: "Listar todas las etiquetas en el foro"
@ -185,7 +185,7 @@ es:
schema: "Buscar esquema de base de datos"
search_settings: "Buscar ajustes del sitio"
dall_e: "Generar imagen usando DALL-E 3"
command_description:
tool_description:
read: "Leyendo: <a href='%{url}'>%{title}</a>"
time: "La hora en %{timezone} es %{time}"
summarize: "Resumido <a href='%{url}'>%{title}</a>"

View File

@ -10,7 +10,7 @@ et:
yaxis: "Date"
discourse_ai:
ai_bot:
command_summary:
tool_summary:
search: "Otsi"
time: "Aeg"
sentiment:

View File

@ -19,7 +19,7 @@ fa_IR:
personas:
dall_e3:
name: "DALL-E 3"
command_summary:
tool_summary:
categories: "فهرست دسته‌بندی‌ها"
search: "جستجو"
tags: "فهرست برچسب‌ها"
@ -28,7 +28,7 @@ fa_IR:
image: "تولید تصویر"
google: "جستجو در گوگل"
dall_e: "تولید تصویر"
command_description:
tool_description:
summarize: "خلاصه شده <a href='%{url}'>%{title}</a>"
dall_e: "%{prompt}"
image: "%{prompt}"

View File

@ -151,7 +151,7 @@ fi:
topic_not_found: "Yhteenveto ei ole saatavilla, ketjua ei löydy!"
summarizing: "Laaditaan yhteenvetoa ketjusta"
searching: "Haetaan: \"%{query}\""
command_options:
tool_options:
search:
max_results:
name: "Tulosten enimmäismäärä"
@ -159,7 +159,7 @@ fi:
base_query:
name: "Perushakukysely"
description: "Peruskysely, jota käytetään haussa. Esimerkki: \"#kiireellinen\" lisää hakukyselyn alkuun \"#kiireellinen\" ja sisältää vain ketjut, joissa on kiireellinen alue tai tunniste."
command_summary:
tool_summary:
categories: "Listaa alueet"
search: "Haku"
tags: "Listaa tunnisteet"
@ -172,7 +172,7 @@ fi:
schema: "Etsi tietokantaskeema"
search_settings: "Haetaan sivustoasetuksia"
dall_e: "Luo kuva"
command_help:
tool_help:
categories: "Listaa kaikki foorumin julkisesti näkyvät alueet"
search: "Hae kaikista foorumin julkisista ketjuista"
tags: "Listaa kaikki foorumin tunnisteet"
@ -185,7 +185,7 @@ fi:
schema: "Etsi tietokantaskeema"
search_settings: "Hae sivustoasetuksia"
dall_e: "Luo kuva DALL-E 3:lla"
command_description:
tool_description:
read: "Luetaan: <a href='%{url}'>%{title}</a>"
time: "Aika aikavyöhykkeellä %{timezone} on %{time}"
summarize: "Yhteenveto: <a href='%{url}'>%{title}</a>"

View File

@ -151,7 +151,7 @@ fr:
topic_not_found: "Résumé indisponible, sujet introuvable !"
summarizing: "Synthèse du sujet"
searching: "Recherche de : '%{query}'"
command_options:
tool_options:
search:
max_results:
name: "Nombre maximal de résultats"
@ -159,7 +159,7 @@ fr:
base_query:
name: "Requête de recherche de base"
description: "Requête de base à utiliser lors de la recherche. Exemple : « #urgent » ajoutera « #urgent » à la requête de recherche et inclura uniquement les sujets avec la catégorie ou l'étiquette correspondante."
command_summary:
tool_summary:
categories: "Lister les catégories"
search: "Rechercher"
tags: "Répertorier les étiquettes"
@ -172,7 +172,7 @@ fr:
schema: "Rechercher le schéma de la base de données"
search_settings: "Recherche des paramètres du site"
dall_e: "Générer une image"
command_help:
tool_help:
categories: "Répertoriez toutes les catégories visibles publiquement sur le forum"
search: "Rechercher dans tous les sujets publics sur le forum"
tags: "Répertorier toutes les étiquettes du forum"
@ -185,7 +185,7 @@ fr:
schema: "Rechercher le schéma de la base de données"
search_settings: "Paramètres du site de recherche"
dall_e: "Générer une image à l'aide de DALL-E 3"
command_description:
tool_description:
read: "Lecture : <a href='%{url}'>%{title}</a>"
time: "L'heure (%{timezone}) est %{time}"
summarize: "Résumé de <a href='%{url}'>%{title}</a>"

View File

@ -10,7 +10,7 @@ gl:
yaxis: "Data"
discourse_ai:
ai_bot:
command_summary:
tool_summary:
search: "Buscar"
time: "Hora"
sentiment:

View File

@ -192,7 +192,7 @@ he:
topic_not_found: "תקציר לא זמין, לא נמצא נושא!"
summarizing: "הנושא מסוכם"
searching: "חיפוש אחר: %{query}"
command_options:
tool_options:
search:
search_private:
name: "חיפוש פרטי"
@ -203,7 +203,7 @@ he:
base_query:
name: "שאילתת חיפוש בסיסית"
description: "שאילתת בסיס לשימוש בעת חיפוש. למשל: #urgent יוסיף את #urgent לשאילתת החיפוש ויכלול רק נושאים עם הקטגוריה או התגית urgent (דחוף)."
command_summary:
tool_summary:
web_browser: "גלישה באינטרנט"
github_search_files: "חיפוש קבצים ב־GitHub"
github_search_code: "חיפוש קוד ב־GitHub"
@ -224,7 +224,7 @@ he:
dall_e: "יצירת תמונה"
search_meta_discourse: "חיפוש ב־Meta Discrouse"
javascript_evaluator: "שערוך JavaScript"
command_help:
tool_help:
web_browser: "גלישה באינטרנט באמצעות בוט בינה מלאכותית"
github_search_code: "חיפוש אחר קוד במאגר GitHub"
github_search_files: "חיפוש אחר קבצים במאגר GitHub"
@ -245,7 +245,7 @@ he:
dall_e: "יצירת תמונה באמצעות DALL-E 3"
search_meta_discourse: "חיפוש ב־Meta Discrouse"
javascript_evaluator: "שערוך JavaScript"
command_description:
tool_description:
web_browser: "קורא את <a href='%{url}'>%{url}</a>"
github_search_files: "בוצע חיפוש אחר %{keywords} בתוך %{repo}/%{branch}"
github_search_code: "בוצע חיפוש אחר %{query} בתוך %{repo}"

View File

@ -10,7 +10,7 @@ hr:
yaxis: "Datum"
discourse_ai:
ai_bot:
command_summary:
tool_summary:
search: "Pretraživanje"
time: "Vrijeme"
summarize: "Rezimirati"

View File

@ -10,7 +10,7 @@ hu:
yaxis: "Dátum"
discourse_ai:
ai_bot:
command_summary:
tool_summary:
search: "Keresés"
tags: "Címkék listázása"
time: "Idő"

View File

@ -10,7 +10,7 @@ hy:
yaxis: "Ամսաթիվ"
discourse_ai:
ai_bot:
command_summary:
tool_summary:
search: "Որոնում"
time: "Ժամ"
sentiment:

View File

@ -71,19 +71,19 @@ id:
ai_bot:
personas:
default_llm_required: "Model LLM default diperlukan sebelum mengaktifkan Obrolan"
command_options:
tool_options:
search:
search_private:
name: "Pencarian Pribadi"
description: "Sertakan semua topik yang dapat diakses pengguna dalam hasil pencarian (secara bawaan hanya topik publik yang disertakan)"
command_summary:
tool_summary:
github_search_files: "File pencarian GitHub"
search: "Cari"
time: "Waktu"
summarize: "Meringkas"
command_help:
tool_help:
github_search_files: "Cari file di repositori GitHub"
summary: "Meringkas suatu topik"
command_description:
tool_description:
github_search_files: "Mencari '%{keywords}' di %{repo}/%{branch}"
github_search_code: "Mencari '%{query}' di %{repo}"

View File

@ -151,7 +151,7 @@ it:
topic_not_found: "Riepilogo non disponibile, argomento non trovato!"
summarizing: "Riepilogo argomento"
searching: "Ricerca di: '%{query}'"
command_options:
tool_options:
search:
max_results:
name: "Numero massimo di risultati"
@ -159,7 +159,7 @@ it:
base_query:
name: "Query di ricerca di base"
description: "Query di base da utilizzare durante la ricerca. Esempio: \"#urgente\" anteporrà \"#urgente\" alla query di ricerca e includerà solo gli argomenti con la categoria o l'etichetta urgente."
command_summary:
tool_summary:
categories: "Elenca le categorie"
search: "Cerca"
tags: "Elenca le etichette"
@ -172,7 +172,7 @@ it:
schema: "Cerca lo schema del database"
search_settings: "Ricerca nelle impostazioni del sito"
dall_e: "Genera immagine"
command_help:
tool_help:
categories: "Elenca tutte le categorie visibili pubblicamente sul forum"
search: "Cerca tutti gli argomenti pubblici sul forum"
tags: "Elenca tutte le etichette sul forum"
@ -185,7 +185,7 @@ it:
schema: "Cerca lo schema del database"
search_settings: "Cerca le impostazioni del sito"
dall_e: "Genera immagine utilizzando DALL-E 3"
command_description:
tool_description:
read: "Lettura: <a href='%{url}'>%{title}</a>"
time: "L'orario in %{timezone} è %{time}"
summarize: "Riassunto di <a href='%{url}'>%{title}</a>"

View File

@ -151,7 +151,7 @@ ja:
topic_not_found: "要約がありません。トピックが見つかりません!"
summarizing: "トピックの要約を生成中"
searching: "検索中: '%{query}'"
command_options:
tool_options:
search:
max_results:
name: "結果の最大件数"
@ -159,7 +159,7 @@ ja:
base_query:
name: "ベース検索クエリ"
description: "検索時に使用するベースクエリ。例: '#urgent' は検索クエリの先頭に '#urgent' を追加し、緊急のカテゴリまたはタグを持つトピックのみが含まれます。"
command_summary:
tool_summary:
categories: "カテゴリをリスト表示"
search: "検索"
tags: "タグをリスト"
@ -172,7 +172,7 @@ ja:
schema: "データベーススキーマを検索"
search_settings: "サイト設定を検索中"
dall_e: "画像を生成"
command_help:
tool_help:
categories: "フォーラムのすべての公開カテゴリをリストします"
search: "フォーラムのすべての公開トピックを検索します"
tags: "フォーラムのすべてのタグをリストします"
@ -185,7 +185,7 @@ ja:
schema: "データベーススキーマを検索します"
search_settings: "サイト設定を検索します"
dall_e: "DALL-E 3 を使って画像を生成します"
command_description:
tool_description:
read: "読み取り中: <a href='%{url}'>%{title}</a>"
time: "%{timezone} の時刻は %{time} です"
summarize: "<a href='%{url}'>%{title}</a> の要約"

View File

@ -10,7 +10,7 @@ ko:
yaxis: "날짜"
discourse_ai:
ai_bot:
command_summary:
tool_summary:
search: "검색"
time: "시간"
summarize: "요약하기"

View File

@ -17,7 +17,7 @@ lt:
image_caption:
attribution: "Antraštė teikiama AI"
ai_bot:
command_summary:
tool_summary:
search: "Paieška"
time: "Laikas"
summarize: "Apibendrinti"

View File

@ -10,6 +10,6 @@ lv:
yaxis: "Datums"
discourse_ai:
ai_bot:
command_summary:
tool_summary:
search: "Meklēt"
time: "Laiks"

View File

@ -10,6 +10,6 @@ nb_NO:
yaxis: "Dato"
discourse_ai:
ai_bot:
command_summary:
tool_summary:
search: "Søk"
time: "Tid"

View File

@ -151,7 +151,7 @@ nl:
topic_not_found: "Samenvatting niet beschikbaar, topic niet gevonden!"
summarizing: "Topic samenvatten"
searching: "Zoeken naar: '%{query}'"
command_options:
tool_options:
search:
max_results:
name: "Maximaal aantal resultaten"
@ -159,7 +159,7 @@ nl:
base_query:
name: "Basiszoekopdracht"
description: "Basisquery om te gebruiken bij het zoeken. Voorbeeld: '#urgent' voegt '#urgent' toe aan de zoekquery en neemt alleen topics mee met de categorie of tag 'urgent'."
command_summary:
tool_summary:
categories: "Categorieën weergeven"
search: "Zoeken"
tags: "Tags weergeven"
@ -172,7 +172,7 @@ nl:
schema: "Databaseschema opzoeken"
search_settings: "Zoeken in site-instellingen"
dall_e: "Afbeelding genereren"
command_help:
tool_help:
categories: "Geef een lijst weer van alle openbaar zichtbare categorieën op het forum"
search: "Doorzoek alle openbare topics op het forum"
tags: "Geef een lijst weer van alle tags op het forum"
@ -185,7 +185,7 @@ nl:
schema: "Zoek een databaseschema op"
search_settings: "Zoek site-instellingen"
dall_e: "Genereer een afbeelding met DALL-E 3"
command_description:
tool_description:
read: "Lezen: <a href='%{url}'>%{title}</a>"
time: "De tijd in %{timezone} is %{time}"
summarize: "<a href='%{url}'>%{title}</a> samengevat"

View File

@ -150,7 +150,7 @@ pl_PL:
topic_not_found: "Podsumowanie niedostępne, nie znaleziono tematu!"
summarizing: "Podsumowanie tematu"
searching: "Wyszukiwanie: '%{query}'"
command_options:
tool_options:
search:
max_results:
name: "Maksymalna liczba wyników"
@ -158,7 +158,7 @@ pl_PL:
base_query:
name: "Podstawowe zapytanie wyszukiwania"
description: "Podstawowe zapytanie używane podczas wyszukiwania. Przykład: '#pilne' spowoduje dodanie '#pilne' do zapytania wyszukiwania i uwzględnienie tylko tematów z kategorią lub tagiem pilne."
command_summary:
tool_summary:
random_picker: "Losowy selektor"
categories: "Wymień kategorie"
search: "Szukaj"
@ -172,7 +172,7 @@ pl_PL:
schema: "Wyszukaj schemat bazy danych"
search_settings: "Wyszukiwanie ustawień witryny"
dall_e: "Wygeneruj obraz"
command_help:
tool_help:
random_picker: "Wybierz losową liczbę lub losowy element listy"
categories: "Wyświetl wszystkie publicznie widoczne kategorie na forum"
search: "Przeszukaj wszystkie publiczne tematy na forum"
@ -186,7 +186,7 @@ pl_PL:
schema: "Wyszukaj schemat bazy danych"
search_settings: "Wyszukaj ustawienia witryny"
dall_e: "Wygeneruj obraz za pomocą DALL-E 3"
command_description:
tool_description:
random_picker: "Wybieranie z %{options}, wybrane: %{result}"
read: "Czytanie: <a href='%{url}'>%{title}</a>"
time: "Czas w %{timezone} wynosi %{time}"

View File

@ -10,7 +10,7 @@ pt:
yaxis: "Data"
discourse_ai:
ai_bot:
command_summary:
tool_summary:
search: "Pesquisar"
time: "Hora"
summarize: "Resumir"

View File

@ -151,7 +151,7 @@ pt_BR:
topic_not_found: "Resumo indisponível, tópico não encontrado!"
summarizing: "Resumindo tópico"
searching: "Pesquisando: \"%{query}\""
command_options:
tool_options:
search:
max_results:
name: "O número máximo de resultados"
@ -159,7 +159,7 @@ pt_BR:
base_query:
name: "Consulta de pesquisa básica"
description: "A consulta de base para usar ao pesquisar. Exemplo: \"#urgent\" precederá \"#urgent\" para a consulta de pesquisa e incluirá apenas tópicos com a etiqueta ou categoria urgente."
command_summary:
tool_summary:
categories: "Listar categorias"
search: "Pesquisar"
tags: "Listar etiquetas"
@ -172,7 +172,7 @@ pt_BR:
schema: "Procurar esquema de banco de dados"
search_settings: "Pesquisando configurações do site"
dall_e: "Gerar imagem"
command_help:
tool_help:
categories: "Listar todas as categorias visíveis publicamente no fórum"
search: "Pesquisar todos os tópicos públicos no fórum"
tags: "Listar todas as etiquetas no fórum"
@ -185,7 +185,7 @@ pt_BR:
schema: "Procurar esquema de banco de dados"
search_settings: "Pesquisar configurações do site"
dall_e: "Gerar imagem usando DALL-E 3"
command_description:
tool_description:
read: "Lendo: <a href='%{url}'>%{title}</a>"
time: "A hora em %{timezone} é %{time}"
summarize: "Resumo de <a href='%{url}'>%{title}</a>"

View File

@ -10,7 +10,7 @@ ro:
yaxis: "Dată"
discourse_ai:
ai_bot:
command_summary:
tool_summary:
search: "Caută"
time: "Oră"
summarize: "Rezumat"

View File

@ -151,7 +151,7 @@ ru:
topic_not_found: "Сводка недоступна: тема не найдена!"
summarizing: "Аналитик темы"
searching: "Поиск: '%{query}'"
command_options:
tool_options:
search:
max_results:
name: "Максимальное количество результатов"
@ -159,7 +159,7 @@ ru:
base_query:
name: "Базовый поисковый запрос"
description: "Базовый запрос, используемый при поиске. Пример: '#urgent' добавит '#urgent' к поисковому запросу и будет включать только темы со срочной категорией или тегом."
command_summary:
tool_summary:
categories: "Вывод списка категорий"
search: "Поиск"
tags: "Вывод списка тегов"
@ -172,7 +172,7 @@ ru:
schema: "Найти схему базы данных"
search_settings: "Поиск настроек сайта"
dall_e: "Сгенерировать изображение"
command_help:
tool_help:
categories: "Вывод всех общедоступных категорий на форуме"
search: "Поиск по всем общедоступным темам на форуме"
tags: "Вывод всех тегов на форуме"
@ -185,7 +185,7 @@ ru:
schema: "Найти схему базы данных"
search_settings: "Настройки поиска по сайту"
dall_e: "Создать изображение с помощью DALL-E 3"
command_description:
tool_description:
read: "Чтение: <a href='%{url}'>%{title}</a>"
time: "Время по часовому поясу %{timezone} — %{time}"
summarize: "Получена сводка: <a href='%{url}'>%{title}</a>"

View File

@ -10,7 +10,7 @@ sk:
yaxis: "Dátum"
discourse_ai:
ai_bot:
command_summary:
tool_summary:
search: "Hľadať"
tags: "Zoznam značiek"
time: "Čas"

View File

@ -10,6 +10,6 @@ sl:
yaxis: "Datum"
discourse_ai:
ai_bot:
command_summary:
tool_summary:
search: "Išči"
time: "Čas"

View File

@ -7,6 +7,6 @@
sq:
discourse_ai:
ai_bot:
command_summary:
tool_summary:
search: "Kërko"
time: "Koha"

View File

@ -7,6 +7,6 @@
sr:
discourse_ai:
ai_bot:
command_summary:
tool_summary:
search: "Pretraži"
time: "Vreme"

View File

@ -10,7 +10,7 @@ sv:
yaxis: "Datum"
discourse_ai:
ai_bot:
command_summary:
tool_summary:
search: "Sök"
time: "Tid"
summarize: "Sammanfatta"

View File

@ -10,6 +10,6 @@ sw:
yaxis: "Tarehe"
discourse_ai:
ai_bot:
command_summary:
tool_summary:
search: "Tafuta"
time: "Muda"

View File

@ -10,7 +10,7 @@ te:
yaxis: "తేదీ"
discourse_ai:
ai_bot:
command_summary:
tool_summary:
search: "వెతుకు"
time: "కాలం"
sentiment:

View File

@ -10,6 +10,6 @@ th:
yaxis: "วันที่"
discourse_ai:
ai_bot:
command_summary:
tool_summary:
search: "ค้นหา"
time: "เวลา"

View File

@ -187,7 +187,7 @@ tr_TR:
topic_not_found: "Özet mevcut değil, konu bulunamadı!"
summarizing: "Konu özetleniyor"
searching: "Aranıyor: '%{query}'"
command_options:
tool_options:
search:
max_results:
name: "Maksimum sonuç sayısı"
@ -195,7 +195,7 @@ tr_TR:
base_query:
name: "Temel Arama Sorgusu"
description: "Arama yaparken kullanılacak temel sorgu. Örnek: '#urgent', arama sorgusuna '#urgent' ekler ve yalnızca acil kategorisine veya etiketine sahip konuları içerir."
command_summary:
tool_summary:
web_browser: "Web'e Gözat"
github_search_code: "GitHub kodu arama"
github_file_content: "GitHub dosya içeriği"
@ -214,7 +214,7 @@ tr_TR:
search_settings: "Site ayarları aranıyor"
dall_e: "Görüntü oluştur"
search_meta_discourse: "Discourse Metada arama yapın"
command_help:
tool_help:
web_browser: "Yapay Zeka Botunu kullanarak web sayfasına göz atın"
github_search_code: "GitHub deposunda kod arama"
github_file_content: "Bir GitHub deposundan dosyaların içeriğini alma"
@ -233,7 +233,7 @@ tr_TR:
search_settings: "Site ayarlarını ara"
dall_e: "DALL-E 3 kullanarak görüntü oluştur"
search_meta_discourse: "Discourse Metada arama yapın"
command_description:
tool_description:
web_browser: "Okunuyor <a href='%{url}'>%{url}</a>"
github_search_code: "%{repo} içinde '%{query}' araması yapıldı"
github_pull_request_diff: "<a href='%{url}'>%{repo} %{pull_id}</a>"

View File

@ -10,7 +10,7 @@ ug:
yaxis: "چېسلا"
discourse_ai:
ai_bot:
command_summary:
tool_summary:
search: "ئىزدە"
tags: "بەلگە تىزىمىنى كۆرسىتىدۇ"
time: "ۋاقىت"

View File

@ -10,7 +10,7 @@ uk:
yaxis: "Дата"
discourse_ai:
ai_bot:
command_summary:
tool_summary:
search: "Пошук"
tags: "Список тегів"
time: "Час"

View File

@ -10,7 +10,7 @@ ur:
yaxis: "تاریخ"
discourse_ai:
ai_bot:
command_summary:
tool_summary:
search: "تلاش کریں"
time: "وقت"
summarize: "خلاصہ"

View File

@ -10,7 +10,7 @@ vi:
yaxis: "Ngày"
discourse_ai:
ai_bot:
command_summary:
tool_summary:
search: "Tìm kiếm"
time: "Thời gian"
summarize: "Tóm tắt"

View File

@ -154,7 +154,7 @@ zh_CN:
topic_not_found: "总结不可用,找不到话题!"
summarizing: "正在总结话题"
searching: "搜索:'%{query}'"
command_options:
tool_options:
search:
max_results:
name: "最大结果数"
@ -162,7 +162,7 @@ zh_CN:
base_query:
name: "基本搜索查询"
description: "搜索时要使用的基本查询。示例:'#urgent' 会在搜索查询前面加上 '#urgent',并且仅包含具有紧急类别或标签的话题。"
command_summary:
tool_summary:
categories: "列出类别"
search: "搜索"
tags: "列出标签"
@ -175,7 +175,7 @@ zh_CN:
schema: "查找数据库架构"
search_settings: "正在搜索站点设置"
dall_e: "生成图片"
command_help:
tool_help:
categories: "列出论坛上所有公开可见的类别"
search: "搜索论坛上的所有公共话题"
tags: "列出论坛上的所有标签"
@ -188,7 +188,7 @@ zh_CN:
schema: "查找数据库架构"
search_settings: "搜索站点设置"
dall_e: "使用 DALL-E 3 生成图片"
command_description:
tool_description:
read: "阅读:<a href='%{url}'>%{title}</a>"
time: "%{timezone} 的时间为 %{time}"
summarize: "已总结 <a href='%{url}'>%{title}</a>"

View File

@ -10,7 +10,7 @@ zh_TW:
yaxis: "日期"
discourse_ai:
ai_bot:
command_summary:
tool_summary:
search: "搜尋"
time: "時間"
summarize: "總結"

View File

@ -32,7 +32,7 @@ DiscourseAi::AiBot::Personas::Persona.system_personas.each do |persona_class, id
persona.system = true
instance = persona_class.new
persona.commands = instance.tools.map { |tool| tool.to_s.split("::").last }
persona.tools = instance.tools.map { |tool| tool.to_s.split("::").last }
persona.system_prompt = instance.system_prompt
persona.top_p = instance.top_p
persona.temperature = instance.temperature

View File

@ -0,0 +1,14 @@
# frozen_string_literal: true
class ToolDetailsAndCommandRemoval < ActiveRecord::Migration[7.0]
def change
add_column :ai_personas, :tool_details, :boolean, default: true, null: false
add_column :ai_personas, :tools, :json, null: false, default: []
Migration::ColumnDropper.mark_readonly(:ai_personas, :commands)
execute <<~SQL
UPDATE ai_personas
SET tools = commands
SQL
end
end

View File

@ -0,0 +1,10 @@
# frozen_string_literal: true
class DropCommandsFromAiPersonas < ActiveRecord::Migration[7.0]
def down
raise ActiveRecord::IrreversibleMigration
end
def up
Migration::ColumnDropper.execute_drop(:ai_personas, [:commands])
end
end

View File

@ -420,11 +420,13 @@ module DiscourseAi
Discourse.redis.setex(redis_stream_key, 60, 1)
end
context[:skip_tool_details] ||= !bot.persona.class.tool_details
new_custom_prompts =
bot.reply(context) do |partial, cancel, placeholder|
reply << partial
raw = reply.dup
raw << "\n\n" << placeholder if placeholder.present?
raw << "\n\n" << placeholder if placeholder.present? && !context[:skip_tool_details]
if stream_reply && !Discourse.redis.get(redis_stream_key)
cancel&.call

View File

@ -13,11 +13,11 @@ module DiscourseAi
end
def localized_name
I18n.t("discourse_ai.ai_bot.command_options.#{tool.signature[:name]}.#{name}.name")
I18n.t("discourse_ai.ai_bot.tool_options.#{tool.signature[:name]}.#{name}.name")
end
def localized_description
I18n.t("discourse_ai.ai_bot.command_options.#{tool.signature[:name]}.#{name}.description")
I18n.t("discourse_ai.ai_bot.tool_options.#{tool.signature[:name]}.#{name}.description")
end
end
end

View File

@ -22,7 +22,7 @@ module DiscourseAi
end
def help
I18n.t("discourse_ai.ai_bot.command_help.#{signature[:name]}")
I18n.t("discourse_ai.ai_bot.tool_help.#{signature[:name]}")
end
def custom_system_message
@ -54,15 +54,15 @@ module DiscourseAi
end
def summary
I18n.t("discourse_ai.ai_bot.command_summary.#{name}")
I18n.t("discourse_ai.ai_bot.tool_summary.#{name}")
end
def details
I18n.t("discourse_ai.ai_bot.command_description.#{name}", description_args)
I18n.t("discourse_ai.ai_bot.tool_description.#{name}", description_args)
end
def help
I18n.t("discourse_ai.ai_bot.command_help.#{name}")
I18n.t("discourse_ai.ai_bot.tool_help.#{name}")
end
def options

View File

@ -226,7 +226,7 @@ RSpec.describe DiscourseAi::AiBot::Personas::Persona do
name: "zzzpun_bot",
description: "you write puns",
system_prompt: "you are pun bot",
commands: ["ImageCommand"],
tools: ["Image"],
allowed_group_ids: [Group::AUTO_GROUPS[:trust_level_0]],
)

View File

@ -290,7 +290,7 @@ RSpec.describe DiscourseAi::AiBot::Playground do
end
it "can run tools" do
persona.update!(commands: ["TimeCommand"])
persona.update!(tools: ["Time"])
responses = [
"<function_calls><invoke><tool_name>time</tool_name><tool_id>time</tool_id><parameters><timezone>Buenos Aires</timezone></parameters></invoke></function_calls>",
@ -590,6 +590,34 @@ RSpec.describe DiscourseAi::AiBot::Playground do
expect(last_post.raw).to include("I found stuff")
end
it "supports disabling tool details" do
persona = Fabricate(:ai_persona, tool_details: false, tools: ["Search"])
bot = DiscourseAi::AiBot::Bot.as(bot_user, persona: persona.class_instance.new)
playground = described_class.new(bot)
response1 = (<<~TXT).strip
<function_calls>
<invoke>
<tool_name>search</tool_name>
<tool_id>search</tool_id>
<parameters>
<search_query>testing various things</search_query>
</parameters>
</invoke>
</function_calls>
TXT
response2 = "I found stuff"
DiscourseAi::Completions::Llm.with_prepared_responses([response1, response2]) do
playground.reply_to(third_post)
end
last_post = third_post.topic.reload.posts.order(:post_number).last
expect(last_post.raw).to eq("I found stuff")
end
it "does not include placeholders in conversation context but includes all completions" do
response1 = (<<~TXT).strip
<function_calls>

View File

@ -7,7 +7,7 @@ RSpec.describe AiPersona do
name: "test",
description: "test",
system_prompt: "test",
commands: [],
tools: [],
allowed_group_ids: [],
)
@ -30,7 +30,7 @@ RSpec.describe AiPersona do
name: "test",
description: "test",
system_prompt: "test",
commands: [],
tools: [],
allowed_group_ids: [],
)
@ -47,7 +47,7 @@ RSpec.describe AiPersona do
name: "test",
description: "test",
system_prompt: "test",
commands: [],
tools: [],
allowed_group_ids: [],
rag_chunk_tokens: 10,
rag_chunk_overlap_tokens: 5,
@ -94,7 +94,7 @@ RSpec.describe AiPersona do
name: "test",
description: "test",
system_prompt: "test",
commands: [],
tools: [],
allowed_group_ids: [],
default_llm: "anthropic:claude-2",
max_context_posts: 3,
@ -135,7 +135,7 @@ RSpec.describe AiPersona do
name: "pun_bot",
description: "you write puns",
system_prompt: "you are pun bot",
commands: ["ImageCommand"],
tools: ["ImageCommand"],
allowed_group_ids: [Group::AUTO_GROUPS[:trust_level_0]],
)

View File

@ -17,7 +17,7 @@ RSpec.describe DiscourseAi::Admin::AiPersonasController do
expect(response).to be_successful
expect(response.parsed_body["ai_personas"].length).to eq(AiPersona.count)
expect(response.parsed_body["meta"]["commands"].length).to eq(
expect(response.parsed_body["meta"]["tools"].length).to eq(
DiscourseAi::AiBot::Personas::Persona.all_available_tools.length,
)
end
@ -33,13 +33,13 @@ RSpec.describe DiscourseAi::Admin::AiPersonasController do
)
end
it "returns commands options with each command" do
persona1 = Fabricate(:ai_persona, name: "search1", commands: ["SearchCommand"])
it "returns tool options with each tool" do
persona1 = Fabricate(:ai_persona, name: "search1", tools: ["SearchCommand"])
persona2 =
Fabricate(
:ai_persona,
name: "search2",
commands: [["SearchCommand", { base_query: "test" }]],
tools: [["SearchCommand", { base_query: "test" }]],
mentionable: true,
default_llm: "anthropic:claude-2",
)
@ -56,36 +56,36 @@ RSpec.describe DiscourseAi::Admin::AiPersonasController do
expect(serializer_persona2["user_id"]).to eq(persona2.user_id)
expect(serializer_persona2["user"]["id"]).to eq(persona2.user_id)
commands = response.parsed_body["meta"]["commands"]
search_command = commands.find { |c| c["id"] == "Search" }
tools = response.parsed_body["meta"]["tools"]
search_tool = tools.find { |c| c["id"] == "Search" }
expect(search_command["help"]).to eq(I18n.t("discourse_ai.ai_bot.command_help.search"))
expect(search_tool["help"]).to eq(I18n.t("discourse_ai.ai_bot.tool_help.search"))
expect(search_command["options"]).to eq(
expect(search_tool["options"]).to eq(
{
"base_query" => {
"type" => "string",
"name" => I18n.t("discourse_ai.ai_bot.command_options.search.base_query.name"),
"name" => I18n.t("discourse_ai.ai_bot.tool_options.search.base_query.name"),
"description" =>
I18n.t("discourse_ai.ai_bot.command_options.search.base_query.description"),
I18n.t("discourse_ai.ai_bot.tool_options.search.base_query.description"),
},
"max_results" => {
"type" => "integer",
"name" => I18n.t("discourse_ai.ai_bot.command_options.search.max_results.name"),
"name" => I18n.t("discourse_ai.ai_bot.tool_options.search.max_results.name"),
"description" =>
I18n.t("discourse_ai.ai_bot.command_options.search.max_results.description"),
I18n.t("discourse_ai.ai_bot.tool_options.search.max_results.description"),
},
"search_private" => {
"type" => "boolean",
"name" => I18n.t("discourse_ai.ai_bot.command_options.search.search_private.name"),
"name" => I18n.t("discourse_ai.ai_bot.tool_options.search.search_private.name"),
"description" =>
I18n.t("discourse_ai.ai_bot.command_options.search.search_private.description"),
I18n.t("discourse_ai.ai_bot.tool_options.search.search_private.description"),
},
},
)
expect(serializer_persona1["commands"]).to eq(["SearchCommand"])
expect(serializer_persona2["commands"]).to eq([["SearchCommand", { "base_query" => "test" }]])
expect(serializer_persona1["tools"]).to eq(["SearchCommand"])
expect(serializer_persona2["tools"]).to eq([["SearchCommand", { "base_query" => "test" }]])
end
context "with translations" do
@ -160,7 +160,7 @@ RSpec.describe DiscourseAi::Admin::AiPersonasController do
name: "superbot",
description: "Assists with tasks",
system_prompt: "you are a helpful bot",
commands: [["search", { "base_query" => "test" }]],
tools: [["search", { "base_query" => "test" }]],
top_p: 0.1,
temperature: 0.5,
mentionable: true,
@ -186,7 +186,7 @@ RSpec.describe DiscourseAi::Admin::AiPersonasController do
persona = AiPersona.find(persona_json["id"])
expect(persona.commands).to eq([["search", { "base_query" => "test" }]])
expect(persona.tools).to eq([["search", { "base_query" => "test" }]])
expect(persona.top_p).to eq(0.1)
expect(persona.temperature).to eq(0.5)
}.to change(AiPersona, :count).by(1)
@ -286,7 +286,7 @@ RSpec.describe DiscourseAi::Admin::AiPersonasController do
ai_persona: {
name: "SuperBot",
enabled: false,
commands: ["search"],
tools: ["search"],
},
}
@ -296,7 +296,7 @@ RSpec.describe DiscourseAi::Admin::AiPersonasController do
ai_persona.reload
expect(ai_persona.name).to eq("SuperBot")
expect(ai_persona.enabled).to eq(false)
expect(ai_persona.commands).to eq(["search"])
expect(ai_persona.tools).to eq(["search"])
end
end
@ -314,11 +314,11 @@ RSpec.describe DiscourseAi::Admin::AiPersonasController do
expect(response.parsed_body["errors"].join).not_to include("en.discourse")
end
it "does not allow editing of commands" do
it "does not allow editing of tools" do
put "/admin/plugins/discourse-ai/ai-personas/#{DiscourseAi::AiBot::Personas::Persona.system_personas.values.first}.json",
params: {
ai_persona: {
commands: %w[SearchCommand ImageCommand],
tools: %w[SearchCommand ImageCommand],
},
}

View File

@ -38,9 +38,9 @@ RSpec.describe "AI personas", type: :system, js: true do
find(".ai-persona-editor__description").fill_in(with: "I am a test persona")
find(".ai-persona-editor__system_prompt").fill_in(with: "You are a helpful bot")
command_selector = PageObjects::Components::SelectKit.new(".ai-persona-editor__commands")
command_selector.expand
command_selector.select_row_by_value("Read")
tool_selector = PageObjects::Components::SelectKit.new(".ai-persona-editor__tools")
tool_selector.expand
tool_selector.select_row_by_value("Read")
find(".ai-persona-editor__save").click()
@ -52,7 +52,7 @@ RSpec.describe "AI personas", type: :system, js: true do
expect(persona.name).to eq("Test Persona")
expect(persona.description).to eq("I am a test persona")
expect(persona.system_prompt).to eq("You are a helpful bot")
expect(persona.commands).to eq(["Read"])
expect(persona.tools).to eq(["Read"])
end
it "will not allow deletion or editing of system personas" do

View File

@ -4,26 +4,22 @@ import AiPersona from "discourse/plugins/discourse-ai/discourse/admin/models/ai-
module("Discourse AI | Unit | Model | ai-persona", function () {
test("init properties", function (assert) {
const properties = {
commands: [
["CommandName", { option1: "value1", option2: "value2" }],
"CommandName2",
"CommandName3",
tools: [
["ToolName", { option1: "value1", option2: "value2" }],
"ToolName2",
"ToolName3",
],
};
const aiPersona = AiPersona.create(properties);
assert.deepEqual(aiPersona.commands, [
"CommandName",
"CommandName2",
"CommandName3",
]);
assert.deepEqual(aiPersona.tools, ["ToolName", "ToolName2", "ToolName3"]);
assert.equal(
aiPersona.getCommandOption("CommandName", "option1").value,
aiPersona.getToolOption("ToolName", "option1").value,
"value1"
);
assert.equal(
aiPersona.getCommandOption("CommandName", "option2").value,
aiPersona.getToolOption("ToolName", "option2").value,
"value2"
);
});
@ -32,7 +28,7 @@ module("Discourse AI | Unit | Model | ai-persona", function () {
const properties = {
id: 1,
name: "Test",
commands: ["CommandName"],
tools: ["ToolName"],
allowed_group_ids: [12],
system: false,
enabled: true,
@ -54,16 +50,17 @@ module("Discourse AI | Unit | Model | ai-persona", function () {
rag_conversation_chunks: 10,
question_consolidator_llm: "Question Consolidator LLM",
allow_chat: false,
tool_details: true,
};
const aiPersona = AiPersona.create({ ...properties });
aiPersona.getCommandOption("CommandName", "option1").value = "value1";
aiPersona.getToolOption("ToolName", "option1").value = "value1";
const updatedProperties = aiPersona.updateProperties();
// perform remapping for save
properties.commands = [["CommandName", { option1: "value1" }]];
properties.tools = [["ToolName", { option1: "value1" }]];
assert.deepEqual(updatedProperties, properties);
});
@ -72,7 +69,7 @@ module("Discourse AI | Unit | Model | ai-persona", function () {
const properties = {
id: 1,
name: "Test",
commands: ["CommandName"],
tools: ["ToolName"],
allowed_group_ids: [12],
system: false,
enabled: true,
@ -94,15 +91,16 @@ module("Discourse AI | Unit | Model | ai-persona", function () {
rag_conversation_chunks: 10,
question_consolidator_llm: "Question Consolidator LLM",
allow_chat: false,
tool_details: true,
};
const aiPersona = AiPersona.create({ ...properties });
aiPersona.getCommandOption("CommandName", "option1").value = "value1";
aiPersona.getToolOption("ToolName", "option1").value = "value1";
const createdProperties = aiPersona.createProperties();
properties.commands = [["CommandName", { option1: "value1" }]];
properties.tools = [["ToolName", { option1: "value1" }]];
assert.deepEqual(createdProperties, properties);
});
@ -110,18 +108,18 @@ module("Discourse AI | Unit | Model | ai-persona", function () {
test("working copy", function (assert) {
const aiPersona = AiPersona.create({
name: "Test",
commands: ["CommandName"],
tools: ["ToolName"],
});
aiPersona.getCommandOption("CommandName", "option1").value = "value1";
aiPersona.getToolOption("ToolName", "option1").value = "value1";
const workingCopy = aiPersona.workingCopy();
assert.equal(workingCopy.name, "Test");
assert.equal(
workingCopy.getCommandOption("CommandName", "option1").value,
workingCopy.getToolOption("ToolName", "option1").value,
"value1"
);
assert.deepEqual(workingCopy.commands, ["CommandName"]);
assert.deepEqual(workingCopy.tools, ["ToolName"]);
});
});