Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9369d9d0d7 |
@@ -1,4 +1,3 @@
|
||||
< 3.5.0.beta2-dev: de8624416a15b3d8e7ad350b083cc1420451ccec
|
||||
< 3.5.0.beta1-dev: bdef136080074a993e7c4f5ca562edc31a8ba756
|
||||
< 3.4.0.beta4-dev: a53719ab8eb071459f215227421b3ea4987e5f87
|
||||
< 3.4.0.beta4-dev: 20612fde52d3f740cad64823ef8aadb0748b567f
|
||||
|
||||
+1
-2
@@ -1,8 +1,7 @@
|
||||
{
|
||||
"tests": {
|
||||
"requiredPlugins": [
|
||||
"https://github.com/discourse/discourse-prometheus",
|
||||
"https://github.com/discourse/discourse-solved.git"
|
||||
"https://github.com/discourse/discourse-prometheus"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
+5
-1
@@ -1,6 +1,10 @@
|
||||
import DiscourseRoute from "discourse/routes/discourse";
|
||||
|
||||
export default class DiscourseAiToolsNewRoute extends DiscourseRoute {
|
||||
beforeModel(transition) {
|
||||
this.preset = transition.to.queryParams.presetId || "empty_tool";
|
||||
}
|
||||
|
||||
async model() {
|
||||
return this.store.createRecord("ai-tool");
|
||||
}
|
||||
@@ -8,10 +12,10 @@ export default class DiscourseAiToolsNewRoute extends DiscourseRoute {
|
||||
setupController(controller) {
|
||||
super.setupController(...arguments);
|
||||
const toolsModel = this.modelFor("adminPlugins.show.discourse-ai-tools");
|
||||
|
||||
controller.set("allTools", toolsModel);
|
||||
controller.set("presets", toolsModel.resultSetMeta.presets);
|
||||
controller.set("llms", toolsModel.resultSetMeta.llms);
|
||||
controller.set("settings", toolsModel.resultSetMeta.settings);
|
||||
controller.set("selectedPreset", this.preset);
|
||||
}
|
||||
}
|
||||
|
||||
+1
@@ -5,5 +5,6 @@
|
||||
@presets={{this.presets}}
|
||||
@llms={{this.llms}}
|
||||
@settings={{this.settings}}
|
||||
@selectedPreset={{this.selectedPreset}}
|
||||
/>
|
||||
</section>
|
||||
@@ -15,7 +15,7 @@ module DiscourseAi
|
||||
LocalizedAiPersonaSerializer.new(persona, root: false)
|
||||
end
|
||||
tools =
|
||||
DiscourseAi::Personas::Persona.all_available_tools.map do |tool|
|
||||
DiscourseAi::AiBot::Personas::Persona.all_available_tools.map do |tool|
|
||||
AiToolSerializer.new(tool, root: false)
|
||||
end
|
||||
AiTool
|
||||
|
||||
@@ -12,11 +12,11 @@ module ::Jobs
|
||||
return if message.blank?
|
||||
|
||||
personaClass =
|
||||
DiscourseAi::Personas::Persona.find_by(id: args[:persona_id], user: message.user)
|
||||
DiscourseAi::AiBot::Personas::Persona.find_by(id: args[:persona_id], user: message.user)
|
||||
return if personaClass.blank?
|
||||
|
||||
user = User.find_by(id: personaClass.user_id)
|
||||
bot = DiscourseAi::Personas::Bot.as(user, persona: personaClass.new)
|
||||
bot = DiscourseAi::AiBot::Bot.as(user, persona: personaClass.new)
|
||||
|
||||
DiscourseAi::AiBot::Playground.new(bot).reply_to_chat_message(
|
||||
message,
|
||||
|
||||
@@ -10,13 +10,13 @@ module ::Jobs
|
||||
persona_id = args[:persona_id]
|
||||
|
||||
begin
|
||||
persona = DiscourseAi::Personas::Persona.find_by(user: post.user, id: persona_id)
|
||||
raise DiscourseAi::Personas::Bot::BOT_NOT_FOUND if persona.nil?
|
||||
persona = DiscourseAi::AiBot::Personas::Persona.find_by(user: post.user, id: persona_id)
|
||||
raise DiscourseAi::AiBot::Bot::BOT_NOT_FOUND if persona.nil?
|
||||
|
||||
bot = DiscourseAi::Personas::Bot.as(bot_user, persona: persona.new)
|
||||
bot = DiscourseAi::AiBot::Bot.as(bot_user, persona: persona.new)
|
||||
|
||||
DiscourseAi::AiBot::Playground.new(bot).reply_to(post)
|
||||
rescue DiscourseAi::Personas::Bot::BOT_NOT_FOUND
|
||||
rescue DiscourseAi::AiBot::Bot::BOT_NOT_FOUND
|
||||
Rails.logger.warn(
|
||||
"Bot not found for post #{post.id} - perhaps persona was deleted or bot was disabled",
|
||||
)
|
||||
|
||||
@@ -19,7 +19,7 @@ module Jobs
|
||||
return if (llm_model = LlmModel.find_by(id: ai_persona_klass.default_llm_id)).nil?
|
||||
|
||||
bot =
|
||||
DiscourseAi::Personas::Bot.as(
|
||||
DiscourseAi::AiBot::Bot.as(
|
||||
Discourse.system_user,
|
||||
persona: ai_persona_klass.new,
|
||||
model: llm_model,
|
||||
|
||||
@@ -201,14 +201,14 @@ class AiPersona < ActiveRecord::Base
|
||||
if inner_name.start_with?("custom-")
|
||||
custom_tool_id = inner_name.split("-", 2).last.to_i
|
||||
if AiTool.exists?(id: custom_tool_id, enabled: true)
|
||||
klass = DiscourseAi::Personas::Tools::Custom.class_instance(custom_tool_id)
|
||||
klass = DiscourseAi::AiBot::Tools::Custom.class_instance(custom_tool_id)
|
||||
end
|
||||
else
|
||||
inner_name = inner_name.gsub("Tool", "")
|
||||
inner_name = "List#{inner_name}" if %w[Categories Tags].include?(inner_name)
|
||||
|
||||
begin
|
||||
klass = "DiscourseAi::Personas::Tools::#{inner_name}".constantize
|
||||
klass = "DiscourseAi::AiBot::Tools::#{inner_name}".constantize
|
||||
options[klass] = current_options if current_options
|
||||
rescue StandardError
|
||||
end
|
||||
@@ -218,7 +218,7 @@ class AiPersona < ActiveRecord::Base
|
||||
klass
|
||||
end
|
||||
|
||||
persona_class = DiscourseAi::Personas::Persona.system_personas_by_id[self.id]
|
||||
persona_class = DiscourseAi::AiBot::Personas::Persona.system_personas_by_id[self.id]
|
||||
if persona_class
|
||||
instance_attributes.each do |key, value|
|
||||
# description/name are localized
|
||||
@@ -230,7 +230,7 @@ class AiPersona < ActiveRecord::Base
|
||||
|
||||
ai_persona_id = self.id
|
||||
|
||||
Class.new(DiscourseAi::Personas::Persona) do
|
||||
Class.new(DiscourseAi::AiBot::Personas::Persona) do
|
||||
instance_attributes.each { |key, value| define_singleton_method(key) { value } }
|
||||
|
||||
define_singleton_method(:to_s) do
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { tracked } from "@glimmer/tracking";
|
||||
import { ajax } from "discourse/lib/ajax";
|
||||
import RestModel from "discourse/models/rest";
|
||||
|
||||
@@ -62,7 +63,40 @@ const SYSTEM_ATTRIBUTES = [
|
||||
"allow_chat_direct_messages",
|
||||
];
|
||||
|
||||
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 tools as an Array.
|
||||
// [[ToolName, {option1: value, option2: value}, force], ToolName2, ToolName3]
|
||||
// So we rework this into a "tools" property and nested toolOptions
|
||||
init(properties) {
|
||||
this.forcedTools = [];
|
||||
if (properties.tools) {
|
||||
properties.tools = properties.tools.map((tool) => {
|
||||
if (typeof tool === "string") {
|
||||
return tool;
|
||||
} else {
|
||||
let [toolId, options, force] = tool;
|
||||
for (let optionId in options) {
|
||||
if (!options.hasOwnProperty(optionId)) {
|
||||
continue;
|
||||
}
|
||||
this.getToolOption(toolId, optionId).value = options[optionId];
|
||||
}
|
||||
if (force) {
|
||||
this.forcedTools.push(toolId);
|
||||
}
|
||||
return toolId;
|
||||
}
|
||||
});
|
||||
}
|
||||
super.init(properties);
|
||||
this.tools = properties.tools;
|
||||
}
|
||||
|
||||
async createUser() {
|
||||
const result = await ajax(
|
||||
`/admin/plugins/discourse-ai/ai-personas/${this.id}/create-user.json`,
|
||||
@@ -75,79 +109,63 @@ export default class AiPersona extends RestModel {
|
||||
return this.user;
|
||||
}
|
||||
|
||||
flattenedToolStructure(data) {
|
||||
return (data.tools || []).map((tName) => {
|
||||
return [tName, data.toolOptions[tName], data.forcedTools.includes(tName)];
|
||||
});
|
||||
getToolOption(toolId, optionId) {
|
||||
this.toolOptions ||= {};
|
||||
this.toolOptions[toolId] ||= {};
|
||||
return (this.toolOptions[toolId][optionId] ||= new ToolOption());
|
||||
}
|
||||
|
||||
// this code is here to convert the wire schema to easier to work with object
|
||||
// on the wire we pass in/out tools as an Array.
|
||||
// [[ToolName, {option1: value, option2: value}, force], ToolName2, ToolName3]
|
||||
// We split it into tools, options and a list of forced ones.
|
||||
populateTools(attrs) {
|
||||
const forcedTools = [];
|
||||
const toolOptions = {};
|
||||
populateToolOptions(attrs) {
|
||||
if (!attrs.tools) {
|
||||
return;
|
||||
}
|
||||
let toolsWithOptions = [];
|
||||
attrs.tools.forEach((toolId) => {
|
||||
if (typeof toolId !== "string") {
|
||||
toolId = toolId[0];
|
||||
}
|
||||
|
||||
const flatTools = attrs.tools?.map((tool) => {
|
||||
if (typeof tool === "string") {
|
||||
return tool;
|
||||
} else {
|
||||
let [toolId, options, force] = tool;
|
||||
const mappedOptions = {};
|
||||
|
||||
for (const optionId in options) {
|
||||
let force = this.forcedTools.includes(toolId);
|
||||
if (this.toolOptions && this.toolOptions[toolId]) {
|
||||
let options = this.toolOptions[toolId];
|
||||
let optionsWithValues = {};
|
||||
for (let optionId in options) {
|
||||
if (!options.hasOwnProperty(optionId)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
mappedOptions[optionId] = options[optionId];
|
||||
let option = options[optionId];
|
||||
optionsWithValues[optionId] = option.value;
|
||||
}
|
||||
|
||||
if (Object.keys(mappedOptions).length > 0) {
|
||||
toolOptions[toolId] = mappedOptions;
|
||||
}
|
||||
|
||||
if (force) {
|
||||
forcedTools.push(toolId);
|
||||
}
|
||||
|
||||
return toolId;
|
||||
toolsWithOptions.push([toolId, optionsWithValues, force]);
|
||||
} else {
|
||||
toolsWithOptions.push([toolId, {}, force]);
|
||||
}
|
||||
});
|
||||
|
||||
attrs.tools = flatTools;
|
||||
attrs.forcedTools = forcedTools;
|
||||
attrs.toolOptions = toolOptions;
|
||||
attrs.tools = toolsWithOptions;
|
||||
}
|
||||
|
||||
updateProperties() {
|
||||
const attrs = this.system
|
||||
let attrs = this.system
|
||||
? this.getProperties(SYSTEM_ATTRIBUTES)
|
||||
: this.getProperties(CREATE_ATTRIBUTES);
|
||||
attrs.id = this.id;
|
||||
|
||||
this.populateToolOptions(attrs);
|
||||
return attrs;
|
||||
}
|
||||
|
||||
createProperties() {
|
||||
return this.getProperties(CREATE_ATTRIBUTES);
|
||||
}
|
||||
|
||||
fromPOJO(data) {
|
||||
const dataClone = JSON.parse(JSON.stringify(data));
|
||||
|
||||
const persona = AiPersona.create(dataClone);
|
||||
persona.tools = this.flattenedToolStructure(dataClone);
|
||||
|
||||
return persona;
|
||||
}
|
||||
|
||||
toPOJO() {
|
||||
const attrs = this.getProperties(CREATE_ATTRIBUTES);
|
||||
this.populateTools(attrs);
|
||||
attrs.forced_tool_count = this.forced_tool_count || -1;
|
||||
|
||||
let attrs = this.getProperties(CREATE_ATTRIBUTES);
|
||||
this.populateToolOptions(attrs);
|
||||
return attrs;
|
||||
}
|
||||
|
||||
workingCopy() {
|
||||
let attrs = this.getProperties(CREATE_ATTRIBUTES);
|
||||
this.populateToolOptions(attrs);
|
||||
|
||||
const persona = AiPersona.create(attrs);
|
||||
persona.forcedTools = (this.forcedTools || []).slice();
|
||||
persona.forced_tool_count = this.forced_tool_count || -1;
|
||||
return persona;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,8 +32,10 @@ export default class AiTool extends RestModel {
|
||||
|
||||
if (parameter.enum && parameter.enum.length) {
|
||||
parameter.enum = new TrackedArray(parameter.enum);
|
||||
parameter.isEnum = true;
|
||||
} else {
|
||||
parameter.enum = null;
|
||||
parameter.isEnum = false;
|
||||
}
|
||||
|
||||
return parameter;
|
||||
|
||||
@@ -23,7 +23,6 @@ import closeOnClickOutside from "discourse/modifiers/close-on-click-outside";
|
||||
import { i18n } from "discourse-i18n";
|
||||
import DTooltip from "float-kit/components/d-tooltip";
|
||||
import DoughnutChart from "discourse/plugins/discourse-ai/discourse/components/doughnut-chart";
|
||||
import AiSentimentHorizontalBar from "../components/ai-sentiment-horizontal-bar";
|
||||
|
||||
export default class AdminReportSentimentAnalysis extends Component {
|
||||
@service router;
|
||||
@@ -83,15 +82,6 @@ export default class AdminReportSentimentAnalysis extends Component {
|
||||
}
|
||||
}
|
||||
|
||||
get groupingType() {
|
||||
const dataSample = this.args.model.data[0];
|
||||
const localePrefix =
|
||||
"discourse_ai.sentiments.sentiment_analysis.group_types";
|
||||
return dataSample.category_name
|
||||
? i18n(`${localePrefix}.category`)
|
||||
: i18n(`${localePrefix}.tag`);
|
||||
}
|
||||
|
||||
get colors() {
|
||||
return ["#2ecc71", "#95a5a6", "#e74c3c"];
|
||||
}
|
||||
@@ -117,17 +107,7 @@ export default class AdminReportSentimentAnalysis extends Component {
|
||||
this.calculateNeutralScore(data),
|
||||
data.negative_count,
|
||||
],
|
||||
score_map: {
|
||||
positive: data.positive_count,
|
||||
neutral: this.calculateNeutralScore(data),
|
||||
negative: data.negative_count,
|
||||
},
|
||||
total_score: data.total_count,
|
||||
widths: {
|
||||
positive: (data.positive_count / data.total_count) * 100,
|
||||
neutral: (this.calculateNeutralScore(data) / data.total_count) * 100,
|
||||
negative: (data.negative_count / data.total_count) * 100,
|
||||
},
|
||||
};
|
||||
});
|
||||
}
|
||||
@@ -236,15 +216,10 @@ export default class AdminReportSentimentAnalysis extends Component {
|
||||
}
|
||||
|
||||
const currentQueryParams = this.router.currentRoute.queryParams;
|
||||
|
||||
const currentFilters = currentQueryParams?.filters
|
||||
? JSON.parse(currentQueryParams.filters)
|
||||
: {};
|
||||
|
||||
this.router.transitionTo(this.router.currentRoute.name, {
|
||||
queryParams: {
|
||||
...currentQueryParams,
|
||||
filters: currentFilters, // avoids a double escaping
|
||||
filters: JSON.parse(currentQueryParams.filters), // avoids a double escaping
|
||||
selectedChart: data.title,
|
||||
},
|
||||
});
|
||||
@@ -290,13 +265,10 @@ export default class AdminReportSentimentAnalysis extends Component {
|
||||
this.posts = [];
|
||||
|
||||
const currentQueryParams = this.router.currentRoute.queryParams;
|
||||
const currentFilters = currentQueryParams?.filters
|
||||
? JSON.parse(currentQueryParams.filters)
|
||||
: {};
|
||||
this.router.transitionTo(this.router.currentRoute.name, {
|
||||
queryParams: {
|
||||
...currentQueryParams,
|
||||
filters: currentFilters, // avoids a double escaping
|
||||
filters: JSON.parse(currentQueryParams.filters), // avoids a double escaping
|
||||
selectedChart: null,
|
||||
},
|
||||
});
|
||||
@@ -322,56 +294,29 @@ export default class AdminReportSentimentAnalysis extends Component {
|
||||
|
||||
{{#unless this.showingSelectedChart}}
|
||||
<div class="admin-report-sentiment-analysis">
|
||||
<table class="sentiment-analysis-table md-table">
|
||||
<thead>
|
||||
<th>{{this.groupingType}}</th>
|
||||
<th>{{i18n
|
||||
"discourse_ai.sentiments.sentiment_analysis.table.total_count"
|
||||
}}</th>
|
||||
<th>{{i18n
|
||||
"discourse_ai.sentiments.sentiment_analysis.table.sentiment"
|
||||
}}</th>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
{{#each this.transformedData as |data|}}
|
||||
<tr
|
||||
class="sentiment-analysis-table__row"
|
||||
role="button"
|
||||
{{on "click" (fn this.showDetails data)}}
|
||||
{{closeOnClickOutside
|
||||
(fn (mut this.selectedChart) null)
|
||||
(hash
|
||||
targetSelector=".admin-report-sentiment-analysis-details"
|
||||
secondaryTargetSelector=".admin-report-sentiment-analysis"
|
||||
)
|
||||
}}
|
||||
>
|
||||
<td class="sentiment-analysis-table__title">{{data.title}}</td>
|
||||
<td
|
||||
class="sentiment-analysis-table__total-score"
|
||||
>{{data.total_score}}</td>
|
||||
<td class="sentiment-horizontal-bar">
|
||||
<AiSentimentHorizontalBar
|
||||
@type="positive"
|
||||
@score={{data.score_map.positive}}
|
||||
@width={{data.widths.positive}}
|
||||
/>
|
||||
<AiSentimentHorizontalBar
|
||||
@type="negative"
|
||||
@score={{data.score_map.negative}}
|
||||
@width={{data.widths.negative}}
|
||||
/>
|
||||
<AiSentimentHorizontalBar
|
||||
@type="neutral"
|
||||
@score={{data.score_map.neutral}}
|
||||
@width={{data.widths.neutral}}
|
||||
/>
|
||||
</td>
|
||||
</tr>
|
||||
{{/each}}
|
||||
</tbody>
|
||||
</table>
|
||||
{{#each this.transformedData as |data|}}
|
||||
<div
|
||||
class="admin-report-sentiment-analysis__chart-wrapper"
|
||||
role="button"
|
||||
{{on "click" (fn this.showDetails data)}}
|
||||
{{closeOnClickOutside
|
||||
(fn (mut this.selectedChart) null)
|
||||
(hash
|
||||
targetSelector=".admin-report-sentiment-analysis-details"
|
||||
secondaryTargetSelector=".admin-report-sentiment-analysis"
|
||||
)
|
||||
}}
|
||||
>
|
||||
<DoughnutChart
|
||||
@labels={{@model.labels}}
|
||||
@colors={{this.colors}}
|
||||
@data={{data.scores}}
|
||||
@totalScore={{data.total_score}}
|
||||
@doughnutTitle={{data.title}}
|
||||
@displayLegend={{true}}
|
||||
/>
|
||||
</div>
|
||||
{{/each}}
|
||||
</div>
|
||||
{{/unless}}
|
||||
|
||||
|
||||
@@ -288,11 +288,6 @@ export default class AiEmbeddingEditor extends Component {
|
||||
});
|
||||
}
|
||||
|
||||
@action
|
||||
providerKeys(providerParams) {
|
||||
return providerParams ? Object.keys(providerParams) : [];
|
||||
}
|
||||
|
||||
<template>
|
||||
{{#if this.showPresets}}
|
||||
<BackButton
|
||||
@@ -498,8 +493,8 @@ export default class AiEmbeddingEditor extends Component {
|
||||
|
||||
{{! provider-specific content }}
|
||||
{{#if this.currentProvider}}
|
||||
<form.Object @name="provider_params" as |object providerData|>
|
||||
{{#each (this.providerKeys providerData) as |name|}}
|
||||
{{#if data.provider_params}}
|
||||
<form.Object @name="provider_params" as |object name|>
|
||||
{{#let (get this.providerParams name) as |params|}}
|
||||
{{#if params}}
|
||||
<object.Field
|
||||
@@ -528,8 +523,8 @@ export default class AiEmbeddingEditor extends Component {
|
||||
</object.Field>
|
||||
{{/if}}
|
||||
{{/let}}
|
||||
{{/each}}
|
||||
</form.Object>
|
||||
</form.Object>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
|
||||
<form.Actions class="ai-embedding-editor__action_panel">
|
||||
@@ -555,24 +550,33 @@ export default class AiEmbeddingEditor extends Component {
|
||||
</form.Actions>
|
||||
|
||||
{{#if this.displayTestResult}}
|
||||
<form.Container @format="full" class="ai-embedding-editor-tests">
|
||||
<ConditionalLoadingSpinner
|
||||
@size="small"
|
||||
@condition={{this.testRunning}}
|
||||
>
|
||||
{{#if this.testResult}}
|
||||
<div class="ai-embedding-editor-tests__success">
|
||||
{{icon "check"}}
|
||||
{{i18n "discourse_ai.embeddings.tests.success"}}
|
||||
</div>
|
||||
{{else}}
|
||||
<div class="ai-embedding-editor-tests__failure">
|
||||
{{icon "xmark"}}
|
||||
{{this.testErrorMessage}}
|
||||
</div>
|
||||
{{/if}}
|
||||
</ConditionalLoadingSpinner>
|
||||
</form.Container>
|
||||
<form.Field
|
||||
@showTitle={{false}}
|
||||
@name="test_results"
|
||||
@title="test_results"
|
||||
@format="full"
|
||||
class="ai-embedding-editor-tests"
|
||||
as |field|
|
||||
>
|
||||
<field.Custom>
|
||||
<ConditionalLoadingSpinner
|
||||
@size="small"
|
||||
@condition={{this.testRunning}}
|
||||
>
|
||||
{{#if this.testResult}}
|
||||
<div class="ai-embedding-editor-tests__success">
|
||||
{{icon "check"}}
|
||||
{{i18n "discourse_ai.embeddings.tests.success"}}
|
||||
</div>
|
||||
{{else}}
|
||||
<div class="ai-embedding-editor-tests__failure">
|
||||
{{icon "xmark"}}
|
||||
{{this.testErrorMessage}}
|
||||
</div>
|
||||
{{/if}}
|
||||
</ConditionalLoadingSpinner>
|
||||
</field.Custom>
|
||||
</form.Field>
|
||||
{{/if}}
|
||||
</Form>
|
||||
{{/if}}
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
import { computed } from "@ember/object";
|
||||
import { i18n } from "discourse-i18n";
|
||||
import ComboBox from "select-kit/components/combo-box";
|
||||
|
||||
export default ComboBox.extend({
|
||||
content: computed(function () {
|
||||
const content = [
|
||||
{
|
||||
id: -1,
|
||||
name: i18n("discourse_ai.ai_persona.tool_strategies.all"),
|
||||
},
|
||||
];
|
||||
|
||||
[1, 2, 5].forEach((i) => {
|
||||
content.push({
|
||||
id: i,
|
||||
name: i18n("discourse_ai.ai_persona.tool_strategies.replies", {
|
||||
count: i,
|
||||
}),
|
||||
});
|
||||
});
|
||||
|
||||
return content;
|
||||
}),
|
||||
|
||||
selectKitOptions: {
|
||||
filterable: false,
|
||||
},
|
||||
});
|
||||
@@ -172,6 +172,9 @@ export default class AiFullPageSearch extends Component {
|
||||
this.AiResults = [];
|
||||
this.showingAiResults = false;
|
||||
this.args.addSearchResults([], "topic_id");
|
||||
this.appEvents.trigger(AI_RESULTS_TOGGLED, {
|
||||
enabled: false,
|
||||
});
|
||||
}
|
||||
|
||||
performHyDESearch() {
|
||||
|
||||
@@ -250,11 +250,6 @@ export default class AiLlmEditorForm extends Component {
|
||||
});
|
||||
}
|
||||
|
||||
@action
|
||||
providerParamsKeys(providerParams) {
|
||||
return providerParams ? Object.keys(providerParams) : [];
|
||||
}
|
||||
|
||||
<template>
|
||||
<Form
|
||||
@onSubmit={{this.save}}
|
||||
@@ -339,36 +334,32 @@ export default class AiLlmEditorForm extends Component {
|
||||
<field.Password />
|
||||
</form.Field>
|
||||
|
||||
<form.Object @name="provider_params" as |object providerParamsData|>
|
||||
{{#each (this.providerParamsKeys providerParamsData) as |name|}}
|
||||
{{#let
|
||||
(get (this.metaProviderParams data.provider) name)
|
||||
as |params|
|
||||
}}
|
||||
<object.Field
|
||||
@name={{name}}
|
||||
@title={{i18n
|
||||
(concat "discourse_ai.llms.provider_fields." name)
|
||||
}}
|
||||
@format="large"
|
||||
as |field|
|
||||
>
|
||||
{{#if (eq params.type "enum")}}
|
||||
<field.Select @includeNone={{false}} as |select|>
|
||||
{{#each params.values as |option|}}
|
||||
<select.Option
|
||||
@value={{option.id}}
|
||||
>{{option.name}}</select.Option>
|
||||
{{/each}}
|
||||
</field.Select>
|
||||
{{else if (eq params.type "checkbox")}}
|
||||
<field.Checkbox />
|
||||
{{else}}
|
||||
<field.Input @type={{params.type}} />
|
||||
{{/if}}
|
||||
</object.Field>
|
||||
{{/let}}
|
||||
{{/each}}
|
||||
<form.Object @name="provider_params" as |object name|>
|
||||
{{#let
|
||||
(get (this.metaProviderParams data.provider) name)
|
||||
as |params|
|
||||
}}
|
||||
<object.Field
|
||||
@name={{name}}
|
||||
@title={{i18n (concat "discourse_ai.llms.provider_fields." name)}}
|
||||
@format="large"
|
||||
as |field|
|
||||
>
|
||||
{{#if (eq params.type "enum")}}
|
||||
<field.Select @includeNone={{false}} as |select|>
|
||||
{{#each params.values as |option|}}
|
||||
<select.Option
|
||||
@value={{option.id}}
|
||||
>{{option.name}}</select.Option>
|
||||
{{/each}}
|
||||
</field.Select>
|
||||
{{else if (eq params.type "checkbox")}}
|
||||
<field.Checkbox />
|
||||
{{else}}
|
||||
<field.Input @type={{params.type}} />
|
||||
{{/if}}
|
||||
</object.Field>
|
||||
{{/let}}
|
||||
</form.Object>
|
||||
|
||||
<form.Field
|
||||
@@ -564,24 +555,32 @@ export default class AiLlmEditorForm extends Component {
|
||||
{{/unless}}
|
||||
|
||||
{{#if this.displayTestResult}}
|
||||
<form.Container @format="full">
|
||||
<ConditionalLoadingSpinner
|
||||
@size="small"
|
||||
@condition={{this.testRunning}}
|
||||
>
|
||||
{{#if this.testResult}}
|
||||
<div class="ai-llm-editor-tests__success">
|
||||
{{icon "check"}}
|
||||
{{i18n "discourse_ai.llms.tests.success"}}
|
||||
</div>
|
||||
{{else}}
|
||||
<div class="ai-llm-editor-tests__failure">
|
||||
{{icon "xmark"}}
|
||||
{{this.testErrorMessage}}
|
||||
</div>
|
||||
{{/if}}
|
||||
</ConditionalLoadingSpinner>
|
||||
</form.Container>
|
||||
<form.Field
|
||||
@showTitle={{false}}
|
||||
@name="test_results"
|
||||
@title="test_results"
|
||||
@format="full"
|
||||
as |field|
|
||||
>
|
||||
<field.Custom>
|
||||
<ConditionalLoadingSpinner
|
||||
@size="small"
|
||||
@condition={{this.testRunning}}
|
||||
>
|
||||
{{#if this.testResult}}
|
||||
<div class="ai-llm-editor-tests__success">
|
||||
{{icon "check"}}
|
||||
{{i18n "discourse_ai.llms.tests.success"}}
|
||||
</div>
|
||||
{{else}}
|
||||
<div class="ai-llm-editor-tests__failure">
|
||||
{{icon "xmark"}}
|
||||
{{this.testErrorMessage}}
|
||||
</div>
|
||||
{{/if}}
|
||||
</ConditionalLoadingSpinner>
|
||||
</field.Custom>
|
||||
</form.Field>
|
||||
{{/if}}
|
||||
</Form>
|
||||
</template>
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
import { hash } from "@ember/helper";
|
||||
import ComboBox from "select-kit/components/combo-box";
|
||||
|
||||
const AiLlmSelector = <template>
|
||||
<ComboBox
|
||||
@value={{@value}}
|
||||
@content={{@llms}}
|
||||
@onChange={{@onChange}}
|
||||
@options={{hash
|
||||
filterable=true
|
||||
none="discourse_ai.ai_persona.no_llm_selected"
|
||||
}}
|
||||
class={{@class}}
|
||||
/>
|
||||
</template>;
|
||||
|
||||
export default AiLlmSelector;
|
||||
@@ -0,0 +1,27 @@
|
||||
import { computed } from "@ember/object";
|
||||
import { observes } from "@ember-decorators/object";
|
||||
import { i18n } from "discourse-i18n";
|
||||
import ComboBox from "select-kit/components/combo-box";
|
||||
import { selectKitOptions } from "select-kit/components/select-kit";
|
||||
|
||||
@selectKitOptions({
|
||||
filterable: true,
|
||||
})
|
||||
export default class AiLlmSelector extends ComboBox {
|
||||
@observes("attrs.disabled")
|
||||
_modelDisabledChanged() {
|
||||
this.selectKit.options.set("disabled", this.get("attrs.disabled.value"));
|
||||
}
|
||||
|
||||
@computed
|
||||
get content() {
|
||||
const blankName =
|
||||
this.attrs.blankName || i18n("discourse_ai.ai_persona.no_llm_selected");
|
||||
return [
|
||||
{
|
||||
id: "blank",
|
||||
name: blankName,
|
||||
},
|
||||
].concat(this.llms);
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,91 @@
|
||||
import Component from "@glimmer/component";
|
||||
import { Input } from "@ember/component";
|
||||
import { on } from "@ember/modifier";
|
||||
import { action } from "@ember/object";
|
||||
import { eq } from "truth-helpers";
|
||||
import { i18n } from "discourse-i18n";
|
||||
import AiLlmSelector from "./ai-llm-selector";
|
||||
|
||||
export default class AiPersonaToolOptionEditor extends Component {
|
||||
get isBoolean() {
|
||||
return this.args.option.type === "boolean";
|
||||
}
|
||||
|
||||
get isEnum() {
|
||||
return this.args.option.type === "enum";
|
||||
}
|
||||
|
||||
get isLlm() {
|
||||
return this.args.option.type === "llm";
|
||||
}
|
||||
|
||||
get selectedValue() {
|
||||
return this.args.option.value.value === "true";
|
||||
}
|
||||
|
||||
get selectedLlm() {
|
||||
if (this.args.option.value.value) {
|
||||
return `custom:${this.args.option.value.value}`;
|
||||
} else {
|
||||
return "blank";
|
||||
}
|
||||
}
|
||||
|
||||
set selectedLlm(value) {
|
||||
if (value === "blank") {
|
||||
this.args.option.value.value = null;
|
||||
} else {
|
||||
this.args.option.value.value = value.replace("custom:", "");
|
||||
}
|
||||
}
|
||||
|
||||
@action
|
||||
onCheckboxChange(event) {
|
||||
this.args.option.value.value = event.target.checked ? "true" : "false";
|
||||
}
|
||||
|
||||
@action
|
||||
onSelectOption(event) {
|
||||
this.args.option.value.value = event.target.value;
|
||||
}
|
||||
|
||||
<template>
|
||||
<div class="control-group ai-persona-tool-option-editor">
|
||||
<label>
|
||||
{{@option.name}}
|
||||
</label>
|
||||
<div class="">
|
||||
{{#if this.isEnum}}
|
||||
<select name="input" {{on "change" this.onSelectOption}}>
|
||||
{{#each @option.values as |value|}}
|
||||
<option value={{value}} selected={{eq value @option.value.value}}>
|
||||
{{value}}
|
||||
</option>
|
||||
{{/each}}
|
||||
</select>
|
||||
{{else if this.isLlm}}
|
||||
<AiLlmSelector
|
||||
class="ai-persona-tool-option-editor__llms"
|
||||
@value={{this.selectedLlm}}
|
||||
@llms={{@llms}}
|
||||
@blankName={{i18n "discourse_ai.ai_persona.use_parent_llm"}}
|
||||
/>
|
||||
{{else if this.isBoolean}}
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={{this.selectedValue}}
|
||||
{{on "click" this.onCheckboxChange}}
|
||||
/>
|
||||
{{@option.description}}
|
||||
{{else}}
|
||||
<Input @value={{@option.value.value}} />
|
||||
{{/if}}
|
||||
</div>
|
||||
{{#unless this.isBoolean}}
|
||||
<div class="ai-persona-tool-option-editor__instructions">
|
||||
{{@option.description}}
|
||||
</div>
|
||||
{{/unless}}
|
||||
</div>
|
||||
</template>
|
||||
}
|
||||
@@ -1,95 +1,82 @@
|
||||
import Component from "@glimmer/component";
|
||||
import { action, get } from "@ember/object";
|
||||
import { eq } from "truth-helpers";
|
||||
import { i18n } from "discourse-i18n";
|
||||
import AiLlmSelector from "./ai-llm-selector";
|
||||
import AiPersonaToolOptionEditor from "./ai-persona-tool-option-editor";
|
||||
|
||||
export default class AiPersonaToolOptions extends Component {
|
||||
get showToolOptions() {
|
||||
const allTools = this.args.allTools;
|
||||
if (!allTools || !this.args.data.tools) {
|
||||
if (!allTools) {
|
||||
return false;
|
||||
}
|
||||
return this.args.data?.tools.any(
|
||||
(tool) => allTools.findBy("id", tool)?.options
|
||||
);
|
||||
|
||||
return this.toolNames.any((tool) => allTools.findBy("id", tool)?.options);
|
||||
}
|
||||
|
||||
get toolsMetadata() {
|
||||
const metatada = {};
|
||||
get toolNames() {
|
||||
if (!this.args.tools) {
|
||||
return [];
|
||||
}
|
||||
return this.args.tools.map((tool) => {
|
||||
if (typeof tool === "string") {
|
||||
return tool;
|
||||
} else {
|
||||
return tool[0];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
this.args.allTools.map((t) => {
|
||||
metatada[t.id] = {
|
||||
name: t.name,
|
||||
...t?.options,
|
||||
};
|
||||
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 metatada;
|
||||
}
|
||||
|
||||
@action
|
||||
formObjectKeys(toolOptions) {
|
||||
return toolOptions ? Object.keys(toolOptions) : [];
|
||||
return options;
|
||||
}
|
||||
|
||||
<template>
|
||||
{{#if this.showToolOptions}}
|
||||
<@form.Container
|
||||
@title={{i18n "discourse_ai.ai_persona.tool_options"}}
|
||||
@direction="column"
|
||||
@format="full"
|
||||
>
|
||||
<@form.Object
|
||||
@name="toolOptions"
|
||||
@title={{i18n "discourse_ai.ai_persona.tool_options"}}
|
||||
as |toolObj optsPerTool|
|
||||
>
|
||||
{{#each (this.formObjectKeys optsPerTool) as |toolId|}}
|
||||
<div class="control-group">
|
||||
<label>{{i18n "discourse_ai.ai_persona.tool_options"}}</label>
|
||||
<div>
|
||||
{{#each this.toolOptions as |toolOption|}}
|
||||
<div class="ai-persona-editor__tool-options">
|
||||
{{#let (get this.toolsMetadata toolId) as |toolMeta|}}
|
||||
<div class="ai-persona-editor__tool-options-name">
|
||||
{{toolMeta.name}}
|
||||
</div>
|
||||
<toolObj.Object @name={{toolId}} as |optionsObj optionData|>
|
||||
{{#each (this.formObjectKeys optionData) as |optionName|}}
|
||||
{{#let (get toolMeta optionName) as |optionMeta|}}
|
||||
<optionsObj.Field
|
||||
@name={{optionName}}
|
||||
@title={{optionMeta.name}}
|
||||
@helpText={{optionMeta.description}}
|
||||
@format="full"
|
||||
as |field|
|
||||
>
|
||||
{{#if (eq optionMeta.type "enum")}}
|
||||
<field.Select @includeNone={{false}} as |select|>
|
||||
{{#each optionsObj.values as |v|}}
|
||||
<select.Option @value={{v}}>{{v}}</select.Option>
|
||||
{{/each}}
|
||||
</field.Select>
|
||||
{{else if (eq optionMeta.type "llm")}}
|
||||
<field.Custom>
|
||||
<AiLlmSelector
|
||||
@value={{field.value}}
|
||||
@llms={{@llms}}
|
||||
@onChange={{field.set}}
|
||||
@class="ai-persona-tool-option-editor__llms"
|
||||
/>
|
||||
</field.Custom>
|
||||
{{else if (eq optionMeta.type "boolean")}}
|
||||
<field.Checkbox />
|
||||
{{else}}
|
||||
<field.Input />
|
||||
{{/if}}
|
||||
</optionsObj.Field>
|
||||
{{/let}}
|
||||
{{/each}}
|
||||
</toolObj.Object>
|
||||
{{/let}}
|
||||
<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}}
|
||||
@llms={{@llms}}
|
||||
/>
|
||||
{{/each}}
|
||||
</div>
|
||||
</div>
|
||||
{{/each}}
|
||||
</@form.Object>
|
||||
</@form.Container>
|
||||
</div>
|
||||
</div>
|
||||
{{/if}}
|
||||
</template>
|
||||
}
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
import { concat } from "@ember/helper";
|
||||
import { htmlSafe } from "@ember/template";
|
||||
import { gt } from "truth-helpers";
|
||||
import { i18n } from "discourse-i18n";
|
||||
import DTooltip from "float-kit/components/d-tooltip";
|
||||
|
||||
const AiSentimentHorizontalBar = <template>
|
||||
{{#if (gt @score 0)}}
|
||||
<DTooltip
|
||||
class={{concat "sentiment-horizontal-bar__" @type}}
|
||||
style={{htmlSafe (concat "width: " @width "%")}}
|
||||
>
|
||||
<:trigger>
|
||||
<span class="sentiment-horizontal-bar__count">
|
||||
{{@score}}
|
||||
</span>
|
||||
</:trigger>
|
||||
<:content>
|
||||
{{i18n
|
||||
(concat
|
||||
"discourse_ai.sentiments.sentiment_analysis.filter_types." @type
|
||||
)
|
||||
}}:
|
||||
{{@score}}
|
||||
</:content>
|
||||
</DTooltip>
|
||||
{{/if}}
|
||||
</template>;
|
||||
|
||||
export default AiSentimentHorizontalBar;
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -1,13 +1,36 @@
|
||||
import Component from "@glimmer/component";
|
||||
import { fn } from "@ember/helper";
|
||||
import { action } from "@ember/object";
|
||||
import { LinkTo } from "@ember/routing";
|
||||
import { service } from "@ember/service";
|
||||
import { eq } from "truth-helpers";
|
||||
import DBreadcrumbsItem from "discourse/components/d-breadcrumbs-item";
|
||||
import DButton from "discourse/components/d-button";
|
||||
import DPageSubheader from "discourse/components/d-page-subheader";
|
||||
import DropdownMenu from "discourse/components/dropdown-menu";
|
||||
import { i18n } from "discourse-i18n";
|
||||
import AdminConfigAreaEmptyList from "admin/components/admin-config-area-empty-list";
|
||||
import DMenu from "float-kit/components/d-menu";
|
||||
|
||||
export default class AiToolListEditor extends Component {
|
||||
@service adminPluginNavManager;
|
||||
@service router;
|
||||
|
||||
get lastIndexOfPresets() {
|
||||
return this.args.tools.resultSetMeta.presets.length - 1;
|
||||
}
|
||||
|
||||
@action
|
||||
routeToNewTool(preset) {
|
||||
return this.router.transitionTo(
|
||||
"adminPlugins.show.discourse-ai-tools.new",
|
||||
{
|
||||
queryParams: {
|
||||
presetId: preset.preset_id,
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
<template>
|
||||
<DBreadcrumbsItem
|
||||
@@ -20,13 +43,33 @@ export default class AiToolListEditor extends Component {
|
||||
@learnMoreUrl="https://meta.discourse.org/t/ai-bot-custom-tools/314103"
|
||||
@descriptionLabel={{i18n "discourse_ai.tools.subheader_description"}}
|
||||
>
|
||||
<:actions as |actions|>
|
||||
<actions.Primary
|
||||
@label="discourse_ai.tools.new"
|
||||
@route="adminPlugins.show.discourse-ai-tools.new"
|
||||
<:actions>
|
||||
<DMenu
|
||||
@triggerClass="btn-primary btn-small ai-tool-list-editor__new-button"
|
||||
@label={{i18n "discourse_ai.tools.new"}}
|
||||
@icon="plus"
|
||||
class="ai-tool-list-editor__new-button"
|
||||
/>
|
||||
@placement="bottom-end"
|
||||
>
|
||||
<:content>
|
||||
<DropdownMenu as |dropdown|>
|
||||
{{#each @tools.resultSetMeta.presets as |preset index|}}
|
||||
{{#if (eq index this.lastIndexOfPresets)}}
|
||||
<dropdown.divider />
|
||||
{{/if}}
|
||||
|
||||
<dropdown.item>
|
||||
<DButton
|
||||
@translatedLabel={{preset.preset_name}}
|
||||
@action={{fn this.routeToNewTool preset}}
|
||||
class="btn-transparent"
|
||||
data-option={{preset.preset_id}}
|
||||
/>
|
||||
</dropdown.item>
|
||||
{{/each}}
|
||||
</DropdownMenu>
|
||||
|
||||
</:content>
|
||||
</DMenu>
|
||||
</:actions>
|
||||
</DPageSubheader>
|
||||
|
||||
|
||||
@@ -1,155 +0,0 @@
|
||||
import Component from "@glimmer/component";
|
||||
import { fn } from "@ember/helper";
|
||||
import { on } from "@ember/modifier";
|
||||
import { action } from "@ember/object";
|
||||
import { TrackedArray, TrackedObject } from "@ember-compat/tracked-built-ins";
|
||||
import DButton from "discourse/components/d-button";
|
||||
import withEventValue from "discourse/helpers/with-event-value";
|
||||
import { i18n } from "discourse-i18n";
|
||||
import ComboBox from "select-kit/components/combo-box";
|
||||
|
||||
const PARAMETER_TYPES = [
|
||||
{ name: "string", id: "string" },
|
||||
{ name: "number", id: "number" },
|
||||
{ name: "boolean", id: "boolean" },
|
||||
{ name: "array", id: "array" },
|
||||
];
|
||||
|
||||
export default class AiToolParameterEditor extends Component {
|
||||
@action
|
||||
addParameter() {
|
||||
this.args.parameters.push(
|
||||
new TrackedObject({
|
||||
name: "",
|
||||
description: "",
|
||||
type: "string",
|
||||
required: false,
|
||||
enum: null,
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
@action
|
||||
removeParameter(parameter) {
|
||||
const index = this.args.parameters.indexOf(parameter);
|
||||
this.args.parameters.splice(index, 1);
|
||||
}
|
||||
|
||||
@action
|
||||
toggleRequired(parameter, event) {
|
||||
parameter.required = event.target.checked;
|
||||
}
|
||||
|
||||
@action
|
||||
toggleEnum(parameter) {
|
||||
if (parameter.enum) {
|
||||
parameter.enum = null;
|
||||
} else {
|
||||
this.addEnumValue(parameter);
|
||||
}
|
||||
}
|
||||
|
||||
@action
|
||||
addEnumValue(parameter) {
|
||||
parameter.enum ||= new TrackedArray();
|
||||
parameter.enum.push("");
|
||||
}
|
||||
|
||||
@action
|
||||
removeEnumValue(parameter, index) {
|
||||
parameter.enum.splice(index, 1);
|
||||
if (parameter.enum.length === 0) {
|
||||
parameter.enum = null;
|
||||
}
|
||||
}
|
||||
|
||||
@action
|
||||
updateEnumValue(parameter, index, event) {
|
||||
parameter.enum[index] = event.target.value;
|
||||
}
|
||||
|
||||
<template>
|
||||
{{#each @parameters as |parameter|}}
|
||||
<div class="ai-tool-parameter">
|
||||
<div class="parameter-row">
|
||||
<input
|
||||
{{on "input" (withEventValue (fn (mut parameter.name)))}}
|
||||
value={{parameter.name}}
|
||||
type="text"
|
||||
placeholder={{i18n "discourse_ai.tools.parameter_name"}}
|
||||
/>
|
||||
<ComboBox @value={{parameter.type}} @content={{PARAMETER_TYPES}} />
|
||||
</div>
|
||||
|
||||
<div class="parameter-row">
|
||||
<input
|
||||
{{on "input" (withEventValue (fn (mut parameter.description)))}}
|
||||
value={{parameter.description}}
|
||||
type="text"
|
||||
placeholder={{i18n "discourse_ai.tools.parameter_description"}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div class="parameter-row">
|
||||
<label>
|
||||
<input
|
||||
{{on "input" (fn this.toggleRequired parameter)}}
|
||||
checked={{parameter.required}}
|
||||
type="checkbox"
|
||||
class="parameter-row__required-toggle"
|
||||
/>
|
||||
{{i18n "discourse_ai.tools.parameter_required"}}
|
||||
</label>
|
||||
|
||||
<label>
|
||||
<input
|
||||
{{on "input" (fn this.toggleEnum parameter)}}
|
||||
checked={{parameter.enum}}
|
||||
type="checkbox"
|
||||
class="parameter-row__enum-toggle"
|
||||
/>
|
||||
{{i18n "discourse_ai.tools.parameter_enum"}}
|
||||
</label>
|
||||
|
||||
<DButton
|
||||
@action={{fn this.removeParameter parameter}}
|
||||
@icon="trash-can"
|
||||
class="btn-danger"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{{#if parameter.enum}}
|
||||
<div class="parameter-enum-values">
|
||||
{{#each parameter.enum as |enumValue enumIndex|}}
|
||||
<div class="enum-value-row">
|
||||
<input
|
||||
{{on "change" (fn this.updateEnumValue parameter enumIndex)}}
|
||||
value={{enumValue}}
|
||||
type="text"
|
||||
placeholder={{i18n "discourse_ai.tools.enum_value"}}
|
||||
/>
|
||||
<DButton
|
||||
@action={{fn this.removeEnumValue parameter enumIndex}}
|
||||
@icon="trash-can"
|
||||
class="btn-danger"
|
||||
/>
|
||||
</div>
|
||||
{{/each}}
|
||||
|
||||
<DButton
|
||||
@action={{fn this.addEnumValue parameter}}
|
||||
@label="discourse_ai.tools.add_enum_value"
|
||||
@icon="plus"
|
||||
/>
|
||||
</div>
|
||||
{{/if}}
|
||||
</div>
|
||||
{{/each}}
|
||||
|
||||
<DButton
|
||||
@action={{this.addParameter}}
|
||||
@label="discourse_ai.tools.add_parameter"
|
||||
@icon="plus"
|
||||
/>
|
||||
</template>
|
||||
}
|
||||
@@ -1,13 +0,0 @@
|
||||
import { hash } from "@ember/helper";
|
||||
import MultiSelect from "select-kit/components/multi-select";
|
||||
|
||||
const AiToolSelector = <template>
|
||||
<MultiSelect
|
||||
@value={{@value}}
|
||||
@onChange={{@onChange}}
|
||||
@content={{@content}}
|
||||
@options={{hash filterable=true allowAny=false disabled=@disabled}}
|
||||
/>
|
||||
</template>;
|
||||
|
||||
export default AiToolSelector;
|
||||
@@ -0,0 +1,18 @@
|
||||
import { readOnly } from "@ember/object/computed";
|
||||
import { observes } from "@ember-decorators/object";
|
||||
import MultiSelectComponent from "select-kit/components/multi-select";
|
||||
import { selectKitOptions } from "select-kit/components/select-kit";
|
||||
|
||||
@selectKitOptions({
|
||||
filterable: true,
|
||||
})
|
||||
export default class AiToolSelector extends MultiSelectComponent {
|
||||
@readOnly("tools") content;
|
||||
|
||||
value = "";
|
||||
|
||||
@observes("attrs.disabled")
|
||||
_modelDisabledChanged() {
|
||||
this.selectKit.options.set("disabled", this.get("attrs.disabled.value"));
|
||||
}
|
||||
}
|
||||
@@ -1,81 +0,0 @@
|
||||
import Component from "@glimmer/component";
|
||||
import { tracked } from "@glimmer/tracking";
|
||||
import { on } from "@ember/modifier";
|
||||
import { action } from "@ember/object";
|
||||
import { i18n } from "discourse-i18n";
|
||||
import AiLlmSelector from "./ai-llm-selector";
|
||||
|
||||
export default class RagOptionsFk extends Component {
|
||||
@tracked showIndexingOptions = false;
|
||||
|
||||
@action
|
||||
toggleIndexingOptions(event) {
|
||||
this.showIndexingOptions = !this.showIndexingOptions;
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
}
|
||||
|
||||
get indexingOptionsText() {
|
||||
return this.showIndexingOptions
|
||||
? i18n("discourse_ai.rag.options.hide_indexing_options")
|
||||
: i18n("discourse_ai.rag.options.show_indexing_options");
|
||||
}
|
||||
|
||||
get visionLlms() {
|
||||
return this.args.llms.filter((llm) => llm.vision_enabled);
|
||||
}
|
||||
|
||||
<template>
|
||||
{{#if @data.rag_uploads}}
|
||||
<a
|
||||
href="#"
|
||||
class="rag-options__indexing-options"
|
||||
{{on "click" this.toggleIndexingOptions}}
|
||||
>{{this.indexingOptionsText}}</a>
|
||||
{{/if}}
|
||||
|
||||
{{#if this.showIndexingOptions}}
|
||||
<@form.Field
|
||||
@name="rag_chunk_tokens"
|
||||
@title={{i18n "discourse_ai.rag.options.rag_chunk_tokens"}}
|
||||
@tooltip={{i18n "discourse_ai.rag.options.rag_chunk_tokens_help"}}
|
||||
@format="large"
|
||||
as |field|
|
||||
>
|
||||
<field.Input @type="number" step="any" lang="en" />
|
||||
</@form.Field>
|
||||
|
||||
<@form.Field
|
||||
@name="rag_chunk_overlap_tokens"
|
||||
@title={{i18n "discourse_ai.rag.options.rag_chunk_tokens"}}
|
||||
@tooltip={{i18n
|
||||
"discourse_ai.rag.options.rag_chunk_overlap_tokens_help"
|
||||
}}
|
||||
@format="large"
|
||||
as |field|
|
||||
>
|
||||
<field.Input @type="number" step="any" lang="en" />
|
||||
</@form.Field>
|
||||
|
||||
{{#if @allowImages}}
|
||||
<@form.Field
|
||||
@name="rag_llm_model_id"
|
||||
@title={{i18n "discourse_ai.rag.options.rag_llm_model"}}
|
||||
@tooltip={{i18n "discourse_ai.rag.options.rag_llm_model_help"}}
|
||||
@format="large"
|
||||
as |field|
|
||||
>
|
||||
<field.Custom>
|
||||
<AiLlmSelector
|
||||
@value={{field.value}}
|
||||
@llms={{this.visionLlms}}
|
||||
@onChange={{field.set}}
|
||||
@class="ai-persona-editor__llms"
|
||||
/>
|
||||
</field.Custom>
|
||||
</@form.Field>
|
||||
{{/if}}
|
||||
{{yield}}
|
||||
{{/if}}
|
||||
</template>
|
||||
}
|
||||
@@ -54,10 +54,11 @@ export default class RagUploader extends Component {
|
||||
this.uppyUpload.cancelAllUploads();
|
||||
}
|
||||
|
||||
this.ragUploads = this.target?.rag_uploads?.slice() || [];
|
||||
this.ragUploads = this.target?.rag_uploads || [];
|
||||
this.filteredUploads = this.ragUploads;
|
||||
|
||||
const targetName = this.targetName || this.target?.constructor?.name;
|
||||
const targetName = this.target?.constructor?.name;
|
||||
|
||||
if (this.ragUploads?.length && this.target?.id) {
|
||||
ajax(
|
||||
`/admin/plugins/discourse-ai/rag-document-fragments/files/status.json?target_type=${targetName}&target_id=${this.target.id}`
|
||||
@@ -126,6 +127,7 @@ export default class RagUploader extends Component {
|
||||
|
||||
<template>
|
||||
<div class="rag-uploader">
|
||||
<h3>{{i18n "discourse_ai.rag.uploads.title"}}</h3>
|
||||
{{#if @allowImages}}
|
||||
<p>{{i18n "discourse_ai.rag.uploads.description_with_images"}}</p>
|
||||
{{else}}
|
||||
@@ -135,7 +137,10 @@ export default class RagUploader extends Component {
|
||||
{{#if this.ragUploads}}
|
||||
<div class="rag-uploader__search-input-container">
|
||||
<div class="rag-uploader__search-input">
|
||||
{{icon "search" class="rag-uploader__search-input__search-icon"}}
|
||||
{{icon
|
||||
"magnifying-glass"
|
||||
class="rag-uploader__search-input__search-icon"
|
||||
}}
|
||||
<Input
|
||||
class="rag-uploader__search-input__input"
|
||||
placeholder={{i18n "discourse_ai.rag.uploads.filter"}}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user