Compare commits
42
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5bfe231486 | ||
|
|
a6b08270c0 | ||
|
|
9a752eff29 | ||
|
|
913314b67d | ||
|
|
9dfae3d472 | ||
|
|
76a48786d9 | ||
|
|
f7536181af | ||
|
|
1dde82eb58 | ||
|
|
50e1bc774a | ||
|
|
2dd7068b46 | ||
|
|
7e4f93724d | ||
|
|
95160c80e3 | ||
|
|
2a8be6e2d7 | ||
|
|
0e2dd7378f | ||
|
|
01eced74a3 | ||
|
|
451f7662b2 | ||
|
|
75a1ade441 | ||
|
|
de8624416a | ||
|
|
b6483e416d | ||
|
|
5bf61ef9e1 | ||
|
|
107f14456b | ||
|
|
269f169abf | ||
|
|
7d7c169afa | ||
|
|
9211b211f5 | ||
|
|
24e6aa52bb | ||
|
|
48fff48aa2 | ||
|
|
bd40076dc1 | ||
|
|
3533cd1acc | ||
|
|
51ca942d7d | ||
|
|
ec8018333e | ||
|
|
6aaf8a0619 | ||
|
|
ac29d3080f | ||
|
|
65503a5038 | ||
|
|
b17c688162 | ||
|
|
e59184b65c | ||
|
|
8f4cd2fcbd | ||
|
|
f4708d4178 | ||
|
|
833f914e34 | ||
|
|
168d9d8eb9 | ||
|
|
3da4f5eac3 | ||
|
|
bb32d0d737 | ||
|
|
339251a371 |
@@ -1,3 +1,4 @@
|
||||
< 3.5.0.beta2-dev: de8624416a15b3d8e7ad350b083cc1420451ccec
|
||||
< 3.5.0.beta1-dev: bdef136080074a993e7c4f5ca562edc31a8ba756
|
||||
< 3.4.0.beta4-dev: a53719ab8eb071459f215227421b3ea4987e5f87
|
||||
< 3.4.0.beta4-dev: 20612fde52d3f740cad64823ef8aadb0748b567f
|
||||
|
||||
+59
-45
@@ -1,79 +1,93 @@
|
||||
GEM
|
||||
remote: https://rubygems.org/
|
||||
specs:
|
||||
activesupport (7.1.3.4)
|
||||
activesupport (8.0.2)
|
||||
base64
|
||||
benchmark (>= 0.3)
|
||||
bigdecimal
|
||||
concurrent-ruby (~> 1.0, >= 1.0.2)
|
||||
concurrent-ruby (~> 1.0, >= 1.3.1)
|
||||
connection_pool (>= 2.2.5)
|
||||
drb
|
||||
i18n (>= 1.6, < 2)
|
||||
logger (>= 1.4.2)
|
||||
minitest (>= 5.1)
|
||||
mutex_m
|
||||
tzinfo (~> 2.0)
|
||||
securerandom (>= 0.3)
|
||||
tzinfo (~> 2.0, >= 2.0.5)
|
||||
uri (>= 0.13.1)
|
||||
ast (2.4.2)
|
||||
base64 (0.2.0)
|
||||
bigdecimal (3.1.8)
|
||||
concurrent-ruby (1.3.3)
|
||||
connection_pool (2.4.1)
|
||||
benchmark (0.4.0)
|
||||
bigdecimal (3.1.9)
|
||||
concurrent-ruby (1.3.5)
|
||||
connection_pool (2.5.0)
|
||||
drb (2.2.1)
|
||||
i18n (1.14.5)
|
||||
i18n (1.14.7)
|
||||
concurrent-ruby (~> 1.0)
|
||||
json (2.7.2)
|
||||
language_server-protocol (3.17.0.3)
|
||||
minitest (5.24.1)
|
||||
mutex_m (0.2.0)
|
||||
parallel (1.25.1)
|
||||
parser (3.3.3.0)
|
||||
json (2.10.2)
|
||||
language_server-protocol (3.17.0.4)
|
||||
lint_roller (1.1.0)
|
||||
logger (1.6.6)
|
||||
minitest (5.25.5)
|
||||
parallel (1.26.3)
|
||||
parser (3.3.7.1)
|
||||
ast (~> 2.4.1)
|
||||
racc
|
||||
prettier_print (1.2.1)
|
||||
racc (1.8.0)
|
||||
rack (3.1.10)
|
||||
racc (1.8.1)
|
||||
rack (3.1.12)
|
||||
rainbow (3.1.1)
|
||||
regexp_parser (2.9.2)
|
||||
rexml (3.3.9)
|
||||
rubocop (1.64.1)
|
||||
regexp_parser (2.10.0)
|
||||
rubocop (1.74.0)
|
||||
json (~> 2.3)
|
||||
language_server-protocol (>= 3.17.0)
|
||||
language_server-protocol (~> 3.17.0.2)
|
||||
lint_roller (~> 1.1.0)
|
||||
parallel (~> 1.10)
|
||||
parser (>= 3.3.0.2)
|
||||
rainbow (>= 2.2.2, < 4.0)
|
||||
regexp_parser (>= 1.8, < 3.0)
|
||||
rexml (>= 3.2.5, < 4.0)
|
||||
rubocop-ast (>= 1.31.1, < 2.0)
|
||||
regexp_parser (>= 2.9.3, < 3.0)
|
||||
rubocop-ast (>= 1.38.0, < 2.0)
|
||||
ruby-progressbar (~> 1.7)
|
||||
unicode-display_width (>= 2.4.0, < 3.0)
|
||||
rubocop-ast (1.31.3)
|
||||
unicode-display_width (>= 2.4.0, < 4.0)
|
||||
rubocop-ast (1.38.1)
|
||||
parser (>= 3.3.1.0)
|
||||
rubocop-capybara (2.21.0)
|
||||
rubocop (~> 1.41)
|
||||
rubocop-discourse (3.8.1)
|
||||
rubocop-capybara (2.22.1)
|
||||
lint_roller (~> 1.1)
|
||||
rubocop (~> 1.72, >= 1.72.1)
|
||||
rubocop-discourse (3.12.1)
|
||||
activesupport (>= 6.1)
|
||||
rubocop (>= 1.59.0)
|
||||
rubocop-capybara (>= 2.0.0)
|
||||
rubocop-factory_bot (>= 2.0.0)
|
||||
rubocop-rails (>= 2.25.0)
|
||||
lint_roller (>= 1.1.0)
|
||||
rubocop (>= 1.73.2)
|
||||
rubocop-capybara (>= 2.22.0)
|
||||
rubocop-factory_bot (>= 2.27.0)
|
||||
rubocop-rails (>= 2.30.3)
|
||||
rubocop-rspec (>= 3.0.1)
|
||||
rubocop-rspec_rails (>= 2.30.0)
|
||||
rubocop-factory_bot (2.26.1)
|
||||
rubocop (~> 1.61)
|
||||
rubocop-rails (2.25.1)
|
||||
rubocop-rspec_rails (>= 2.31.0)
|
||||
rubocop-factory_bot (2.27.1)
|
||||
lint_roller (~> 1.1)
|
||||
rubocop (~> 1.72, >= 1.72.1)
|
||||
rubocop-rails (2.30.3)
|
||||
activesupport (>= 4.2.0)
|
||||
lint_roller (~> 1.1)
|
||||
rack (>= 1.1)
|
||||
rubocop (>= 1.33.0, < 2.0)
|
||||
rubocop-ast (>= 1.31.1, < 2.0)
|
||||
rubocop-rspec (3.0.2)
|
||||
rubocop (~> 1.61)
|
||||
rubocop-rspec_rails (2.30.0)
|
||||
rubocop (~> 1.61)
|
||||
rubocop-rspec (~> 3, >= 3.0.1)
|
||||
rubocop (>= 1.72.1, < 2.0)
|
||||
rubocop-ast (>= 1.38.0, < 2.0)
|
||||
rubocop-rspec (3.5.0)
|
||||
lint_roller (~> 1.1)
|
||||
rubocop (~> 1.72, >= 1.72.1)
|
||||
rubocop-rspec_rails (2.31.0)
|
||||
lint_roller (~> 1.1)
|
||||
rubocop (~> 1.72, >= 1.72.1)
|
||||
rubocop-rspec (~> 3.5)
|
||||
ruby-progressbar (1.13.0)
|
||||
securerandom (0.4.1)
|
||||
syntax_tree (6.2.0)
|
||||
prettier_print (>= 1.2.0)
|
||||
tzinfo (2.0.6)
|
||||
concurrent-ruby (~> 1.0)
|
||||
unicode-display_width (2.5.0)
|
||||
unicode-display_width (3.1.4)
|
||||
unicode-emoji (~> 4.0, >= 4.0.4)
|
||||
unicode-emoji (4.0.4)
|
||||
uri (1.0.3)
|
||||
|
||||
PLATFORMS
|
||||
ruby
|
||||
@@ -83,4 +97,4 @@ DEPENDENCIES
|
||||
syntax_tree
|
||||
|
||||
BUNDLED WITH
|
||||
2.5.10
|
||||
2.6.6
|
||||
|
||||
+2
-1
@@ -1,7 +1,8 @@
|
||||
{
|
||||
"tests": {
|
||||
"requiredPlugins": [
|
||||
"https://github.com/discourse/discourse-prometheus"
|
||||
"https://github.com/discourse/discourse-prometheus",
|
||||
"https://github.com/discourse/discourse-solved.git"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,7 +15,7 @@ module DiscourseAi
|
||||
LocalizedAiPersonaSerializer.new(persona, root: false)
|
||||
end
|
||||
tools =
|
||||
DiscourseAi::AiBot::Personas::Persona.all_available_tools.map do |tool|
|
||||
DiscourseAi::Personas::Persona.all_available_tools.map do |tool|
|
||||
AiToolSerializer.new(tool, root: false)
|
||||
end
|
||||
AiTool
|
||||
|
||||
@@ -53,7 +53,12 @@ module DiscourseAi
|
||||
end
|
||||
|
||||
def suggest_title
|
||||
input = get_text_param!
|
||||
if params[:topic_id]
|
||||
topic = Topic.find_by(id: params[:topic_id])
|
||||
input = DiscourseAi::Summarization::Strategies::TopicSummary.new(topic).targets_data
|
||||
else
|
||||
input = get_text_param!
|
||||
end
|
||||
|
||||
prompt = CompletionPrompt.enabled_by_name("generate_titles")
|
||||
raise Discourse::InvalidParameters.new(:mode) if !prompt
|
||||
@@ -73,22 +78,26 @@ module DiscourseAi
|
||||
end
|
||||
|
||||
def suggest_category
|
||||
input = get_text_param!
|
||||
input_hash = { text: input }
|
||||
if params[:topic_id]
|
||||
opts = { topic_id: params[:topic_id] }
|
||||
else
|
||||
input = get_text_param!
|
||||
opts = { text: input }
|
||||
end
|
||||
|
||||
render json:
|
||||
DiscourseAi::AiHelper::SemanticCategorizer.new(
|
||||
input_hash,
|
||||
current_user,
|
||||
).categories,
|
||||
render json: DiscourseAi::AiHelper::SemanticCategorizer.new(current_user, opts).categories,
|
||||
status: 200
|
||||
end
|
||||
|
||||
def suggest_tags
|
||||
input = get_text_param!
|
||||
input_hash = { text: input }
|
||||
if params[:topic_id]
|
||||
opts = { topic_id: params[:topic_id] }
|
||||
else
|
||||
input = get_text_param!
|
||||
opts = { text: input }
|
||||
end
|
||||
|
||||
render json: DiscourseAi::AiHelper::SemanticCategorizer.new(input_hash, current_user).tags,
|
||||
render json: DiscourseAi::AiHelper::SemanticCategorizer.new(current_user, opts).tags,
|
||||
status: 200
|
||||
end
|
||||
|
||||
|
||||
@@ -12,11 +12,11 @@ module ::Jobs
|
||||
return if message.blank?
|
||||
|
||||
personaClass =
|
||||
DiscourseAi::AiBot::Personas::Persona.find_by(id: args[:persona_id], user: message.user)
|
||||
DiscourseAi::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::AiBot::Bot.as(user, persona: personaClass.new)
|
||||
bot = DiscourseAi::Personas::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::AiBot::Personas::Persona.find_by(user: post.user, id: persona_id)
|
||||
raise DiscourseAi::AiBot::Bot::BOT_NOT_FOUND if persona.nil?
|
||||
persona = DiscourseAi::Personas::Persona.find_by(user: post.user, id: persona_id)
|
||||
raise DiscourseAi::Personas::Bot::BOT_NOT_FOUND if persona.nil?
|
||||
|
||||
bot = DiscourseAi::AiBot::Bot.as(bot_user, persona: persona.new)
|
||||
bot = DiscourseAi::Personas::Bot.as(bot_user, persona: persona.new)
|
||||
|
||||
DiscourseAi::AiBot::Playground.new(bot).reply_to(post)
|
||||
rescue DiscourseAi::AiBot::Bot::BOT_NOT_FOUND
|
||||
rescue DiscourseAi::Personas::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::AiBot::Bot.as(
|
||||
DiscourseAi::Personas::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::AiBot::Tools::Custom.class_instance(custom_tool_id)
|
||||
klass = DiscourseAi::Personas::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::AiBot::Tools::#{inner_name}".constantize
|
||||
klass = "DiscourseAi::Personas::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::AiBot::Personas::Persona.system_personas_by_id[self.id]
|
||||
persona_class = DiscourseAi::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::AiBot::Personas::Persona) do
|
||||
Class.new(DiscourseAi::Personas::Persona) do
|
||||
instance_attributes.each { |key, value| define_singleton_method(key) { value } }
|
||||
|
||||
define_singleton_method(:to_s) do
|
||||
|
||||
@@ -26,17 +26,23 @@ class LlmModel < ActiveRecord::Base
|
||||
access_key_id: :text,
|
||||
region: :text,
|
||||
disable_native_tools: :checkbox,
|
||||
disable_temperature: :checkbox,
|
||||
disable_top_p: :checkbox,
|
||||
enable_reasoning: :checkbox,
|
||||
reasoning_tokens: :number,
|
||||
},
|
||||
anthropic: {
|
||||
disable_native_tools: :checkbox,
|
||||
disable_temperature: :checkbox,
|
||||
disable_top_p: :checkbox,
|
||||
enable_reasoning: :checkbox,
|
||||
reasoning_tokens: :number,
|
||||
},
|
||||
open_ai: {
|
||||
organization: :text,
|
||||
disable_native_tools: :checkbox,
|
||||
disable_temperature: :checkbox,
|
||||
disable_top_p: :checkbox,
|
||||
disable_streaming: :checkbox,
|
||||
reasoning_effort: {
|
||||
type: :enum,
|
||||
@@ -69,6 +75,8 @@ class LlmModel < ActiveRecord::Base
|
||||
provider_order: :text,
|
||||
provider_quantizations: :text,
|
||||
disable_streaming: :checkbox,
|
||||
disable_temperature: :checkbox,
|
||||
disable_top_p: :checkbox,
|
||||
},
|
||||
}
|
||||
end
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { tracked } from "@glimmer/tracking";
|
||||
import { ajax } from "discourse/lib/ajax";
|
||||
import RestModel from "discourse/models/rest";
|
||||
|
||||
@@ -63,40 +62,7 @@ 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`,
|
||||
@@ -109,63 +75,79 @@ export default class AiPersona extends RestModel {
|
||||
return this.user;
|
||||
}
|
||||
|
||||
getToolOption(toolId, optionId) {
|
||||
this.toolOptions ||= {};
|
||||
this.toolOptions[toolId] ||= {};
|
||||
return (this.toolOptions[toolId][optionId] ||= new ToolOption());
|
||||
flattenedToolStructure(data) {
|
||||
return (data.tools || []).map((tName) => {
|
||||
return [tName, data.toolOptions[tName], data.forcedTools.includes(tName)];
|
||||
});
|
||||
}
|
||||
|
||||
populateToolOptions(attrs) {
|
||||
if (!attrs.tools) {
|
||||
return;
|
||||
}
|
||||
let toolsWithOptions = [];
|
||||
attrs.tools.forEach((toolId) => {
|
||||
if (typeof toolId !== "string") {
|
||||
toolId = toolId[0];
|
||||
}
|
||||
// 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 = {};
|
||||
|
||||
let force = this.forcedTools.includes(toolId);
|
||||
if (this.toolOptions && this.toolOptions[toolId]) {
|
||||
let options = this.toolOptions[toolId];
|
||||
let optionsWithValues = {};
|
||||
for (let optionId in options) {
|
||||
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) {
|
||||
if (!options.hasOwnProperty(optionId)) {
|
||||
continue;
|
||||
}
|
||||
let option = options[optionId];
|
||||
optionsWithValues[optionId] = option.value;
|
||||
|
||||
mappedOptions[optionId] = options[optionId];
|
||||
}
|
||||
toolsWithOptions.push([toolId, optionsWithValues, force]);
|
||||
} else {
|
||||
toolsWithOptions.push([toolId, {}, force]);
|
||||
|
||||
if (Object.keys(mappedOptions).length > 0) {
|
||||
toolOptions[toolId] = mappedOptions;
|
||||
}
|
||||
|
||||
if (force) {
|
||||
forcedTools.push(toolId);
|
||||
}
|
||||
|
||||
return toolId;
|
||||
}
|
||||
});
|
||||
attrs.tools = toolsWithOptions;
|
||||
|
||||
attrs.tools = flatTools;
|
||||
attrs.forcedTools = forcedTools;
|
||||
attrs.toolOptions = toolOptions;
|
||||
}
|
||||
|
||||
updateProperties() {
|
||||
let attrs = this.system
|
||||
const attrs = this.system
|
||||
? this.getProperties(SYSTEM_ATTRIBUTES)
|
||||
: this.getProperties(CREATE_ATTRIBUTES);
|
||||
attrs.id = this.id;
|
||||
this.populateToolOptions(attrs);
|
||||
|
||||
return attrs;
|
||||
}
|
||||
|
||||
createProperties() {
|
||||
let attrs = this.getProperties(CREATE_ATTRIBUTES);
|
||||
this.populateToolOptions(attrs);
|
||||
return attrs;
|
||||
return this.getProperties(CREATE_ATTRIBUTES);
|
||||
}
|
||||
|
||||
workingCopy() {
|
||||
let attrs = this.getProperties(CREATE_ATTRIBUTES);
|
||||
this.populateToolOptions(attrs);
|
||||
fromPOJO(data) {
|
||||
const dataClone = JSON.parse(JSON.stringify(data));
|
||||
|
||||
const persona = AiPersona.create(dataClone);
|
||||
persona.tools = this.flattenedToolStructure(dataClone);
|
||||
|
||||
const persona = AiPersona.create(attrs);
|
||||
persona.forcedTools = (this.forcedTools || []).slice();
|
||||
persona.forced_tool_count = this.forced_tool_count || -1;
|
||||
return persona;
|
||||
}
|
||||
|
||||
toPOJO() {
|
||||
const attrs = this.getProperties(CREATE_ATTRIBUTES);
|
||||
this.populateTools(attrs);
|
||||
attrs.forced_tool_count = this.forced_tool_count || -1;
|
||||
|
||||
return attrs;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,6 +23,7 @@ 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;
|
||||
@@ -82,6 +83,15 @@ 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"];
|
||||
}
|
||||
@@ -107,7 +117,17 @@ 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,
|
||||
},
|
||||
};
|
||||
});
|
||||
}
|
||||
@@ -216,10 +236,15 @@ 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: JSON.parse(currentQueryParams.filters), // avoids a double escaping
|
||||
filters: currentFilters, // avoids a double escaping
|
||||
selectedChart: data.title,
|
||||
},
|
||||
});
|
||||
@@ -265,9 +290,13 @@ 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
|
||||
selectedChart: null,
|
||||
},
|
||||
});
|
||||
@@ -293,29 +322,56 @@ export default class AdminReportSentimentAnalysis extends Component {
|
||||
|
||||
{{#unless this.showingSelectedChart}}
|
||||
<div class="admin-report-sentiment-analysis">
|
||||
{{#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}}
|
||||
<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>
|
||||
</div>
|
||||
{{/unless}}
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@ import getURL from "discourse/lib/get-url";
|
||||
|
||||
export default class AiArtifactComponent extends Component {
|
||||
@service siteSettings;
|
||||
|
||||
@tracked expanded = false;
|
||||
@tracked showingArtifact = false;
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@ export default class AiComposerHelperMenu extends Component {
|
||||
@service siteSettings;
|
||||
@service currentUser;
|
||||
@service site;
|
||||
|
||||
@tracked newSelectedText;
|
||||
@tracked diff;
|
||||
@tracked customPromptValue = "";
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,29 +0,0 @@
|
||||
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,9 +172,6 @@ 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,6 +250,11 @@ export default class AiLlmEditorForm extends Component {
|
||||
});
|
||||
}
|
||||
|
||||
@action
|
||||
providerParamsKeys(providerParams) {
|
||||
return providerParams ? Object.keys(providerParams) : [];
|
||||
}
|
||||
|
||||
<template>
|
||||
<Form
|
||||
@onSubmit={{this.save}}
|
||||
@@ -334,32 +339,36 @@ export default class AiLlmEditorForm extends Component {
|
||||
<field.Password />
|
||||
</form.Field>
|
||||
|
||||
<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 @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>
|
||||
|
||||
<form.Field
|
||||
@@ -555,32 +564,24 @@ export default class AiLlmEditorForm extends Component {
|
||||
{{/unless}}
|
||||
|
||||
{{#if this.displayTestResult}}
|
||||
<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>
|
||||
<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>
|
||||
{{/if}}
|
||||
</Form>
|
||||
</template>
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
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;
|
||||
@@ -1,27 +0,0 @@
|
||||
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
@@ -1,91 +0,0 @@
|
||||
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,82 +1,95 @@
|
||||
import Component from "@glimmer/component";
|
||||
import { action, get } from "@ember/object";
|
||||
import { eq } from "truth-helpers";
|
||||
import { i18n } from "discourse-i18n";
|
||||
import AiPersonaToolOptionEditor from "./ai-persona-tool-option-editor";
|
||||
import AiLlmSelector from "./ai-llm-selector";
|
||||
|
||||
export default class AiPersonaToolOptions extends Component {
|
||||
get showToolOptions() {
|
||||
const allTools = this.args.allTools;
|
||||
if (!allTools) {
|
||||
if (!allTools || !this.args.data.tools) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return this.toolNames.any((tool) => allTools.findBy("id", tool)?.options);
|
||||
return this.args.data?.tools.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 toolsMetadata() {
|
||||
const metatada = {};
|
||||
|
||||
this.args.allTools.map((t) => {
|
||||
metatada[t.id] = {
|
||||
name: t.name,
|
||||
...t?.options,
|
||||
};
|
||||
});
|
||||
|
||||
return metatada;
|
||||
}
|
||||
|
||||
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;
|
||||
@action
|
||||
formObjectKeys(toolOptions) {
|
||||
return toolOptions ? Object.keys(toolOptions) : [];
|
||||
}
|
||||
|
||||
<template>
|
||||
{{#if this.showToolOptions}}
|
||||
<div class="control-group">
|
||||
<label>{{i18n "discourse_ai.ai_persona.tool_options"}}</label>
|
||||
<div>
|
||||
{{#each this.toolOptions as |toolOption|}}
|
||||
<@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="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}}
|
||||
@llms={{@llms}}
|
||||
/>
|
||||
{{/each}}
|
||||
</div>
|
||||
{{#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>
|
||||
{{/each}}
|
||||
</div>
|
||||
</div>
|
||||
</@form.Object>
|
||||
</@form.Container>
|
||||
{{/if}}
|
||||
</template>
|
||||
}
|
||||
|
||||
@@ -0,0 +1,49 @@
|
||||
import Component from "@glimmer/component";
|
||||
import { service } from "@ember/service";
|
||||
import DButton from "discourse/components/d-button";
|
||||
import icon from "discourse/helpers/d-icon";
|
||||
import { i18n } from "discourse-i18n";
|
||||
import DTooltip from "float-kit/components/d-tooltip";
|
||||
|
||||
export default class AiSearchDiscoveriesTooltip extends Component {
|
||||
@service discobotDiscoveries;
|
||||
|
||||
<template>
|
||||
<span class="ai-search-discoveries-tooltip">
|
||||
<DTooltip @placement="top-end" @interactive={{true}}>
|
||||
<:trigger>
|
||||
{{icon "circle-info"}}
|
||||
</:trigger>
|
||||
<:content>
|
||||
<div class="ai-search-discoveries-tooltip__content">
|
||||
<div class="ai-search-discoveries-tooltip__header">
|
||||
{{i18n "discourse_ai.discobot_discoveries.tooltip.header"}}
|
||||
</div>
|
||||
|
||||
<div class="ai-search-discoveries-tooltip__description">
|
||||
{{#if this.discobotDiscoveries.modelUsed}}
|
||||
{{i18n
|
||||
"discourse_ai.discobot_discoveries.tooltip.content"
|
||||
model=this.discobotDiscoveries.modelUsed
|
||||
}}
|
||||
{{/if}}
|
||||
</div>
|
||||
|
||||
<div class="ai-search-discoveries-tooltip__actions">
|
||||
<DButton
|
||||
class="btn-transparent btn-primary"
|
||||
@label="discourse_ai.discobot_discoveries.tooltip.actions.info"
|
||||
@href="https://meta.discourse.org/t/conversational-ai-search-coming-to-discourse-ai/355939"
|
||||
/>
|
||||
<DButton
|
||||
class="btn-transparent btn-danger"
|
||||
@label="discourse_ai.discobot_discoveries.tooltip.actions.disable"
|
||||
@action={{this.discobotDiscoveries.disableDiscoveries}}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</:content>
|
||||
</DTooltip>
|
||||
</span>
|
||||
</template>
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
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;
|
||||
@@ -0,0 +1,13 @@
|
||||
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;
|
||||
@@ -1,18 +0,0 @@
|
||||
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"));
|
||||
}
|
||||
}
|
||||
@@ -23,6 +23,7 @@ import ComboBox from "select-kit/components/combo-box";
|
||||
|
||||
export default class AiUsage extends Component {
|
||||
@service store;
|
||||
|
||||
@tracked startDate = moment().subtract(30, "days").toDate();
|
||||
@tracked endDate = new Date();
|
||||
@tracked data = this.args.model;
|
||||
|
||||
@@ -13,6 +13,7 @@ import AiIndicatorWave from "../ai-indicator-wave";
|
||||
|
||||
export default class ModalDiffModal extends Component {
|
||||
@service currentUser;
|
||||
|
||||
@tracked loading = false;
|
||||
@tracked diff;
|
||||
@tracked suggestion = "";
|
||||
|
||||
@@ -13,6 +13,7 @@ import { i18n } from "discourse-i18n";
|
||||
|
||||
export default class ShareModal extends Component {
|
||||
@service toasts;
|
||||
|
||||
@tracked shareKey = "";
|
||||
|
||||
constructor() {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user