Compare commits

..
Author SHA1 Message Date
Martin Brennan 9369d9d0d7 WIP: Adding Enum button back to AI tools parameters form 2025-03-18 16:41:41 +10:00
Natalie Tay 5bf61ef9e1 DEV: Support multiple tests per eval and followups per test (#1199)
See https://github.com/discourse/discourse-ai-evals/pull/9 for format of prompts
2025-03-18 11:42:05 +08:00
Keegan George 107f14456b DEV: Convert tool editor to form kit (#1135)
* DEV: Make tool presets a dropdown

* DEV: Select tool presets via DMenu instead

* WIP

* WIP: Add parameter types, uploader, script, etc.

* WIP

* updates

* fix lint

* FIX: spec

* fixes
2025-03-17 11:38:25 -03:00
Sam 269f169abf DEV: add API to get topic info from a custom tool (#1197)
Previously we could only get post info
2025-03-17 18:47:23 +11:00
Jarek Radosz 7d7c169afa DEV: Update linting (#1194) 2025-03-17 15:14:53 +11:00
SamandNatalie Tay 9211b211f5 FEATURE: silent triage using ai persona (#1193)
This allows for a new mode in persona triage where nothing is posted on topics.

This allows people to perform all triage actions using tools

Additionally introduces new APIs to create chat messages from tools which can be useful in certain moderation scenarios

Co-authored-by: Natalie Tay <[email protected]>

* remove TODO code

---------

Co-authored-by: Natalie Tay <[email protected]>
2025-03-17 15:14:09 +11:00
Kris 24e6aa52bb UX: try AI search to side on large screens (#1196) 2025-03-14 14:11:02 -04:00
Keegan George 48fff48aa2 FIX: failing image caption spec (#1195) 2025-03-14 10:19:04 -07:00
Kris bd40076dc1 FIX: need to create model when testing new embedding (#1192) 2025-03-14 13:11:44 -04:00
Natalie Tay 3533cd1acc DEV: Allow prompt-type evals to take in several prompts and messages (#1190)
* DEV: Allow prompt-type evals to take in several prompts and messages

* ❄️
2025-03-14 15:46:22 +11:00
Kris 51ca942d7d REFACTOR: update embeddings to formkit (#1188) 2025-03-13 11:27:38 -04:00
Jarek Radosz ec8018333e DEV: Update linting (#1191) 2025-03-13 13:25:38 +00:00
Keegan George 6aaf8a0619 DEV: Use existing topic embeddings when suggesting tags/categories on edit (#1189)
When editing a topic (instead of creating one) and using the
tag/category suggestion buttons. We want to use existing topic
embeddings instead of creating new ones.
2025-03-12 18:52:07 -07:00
Penar Musaraj ac29d3080f DEV: Fix SCSS linting issue (#1187) 2025-03-12 13:25:17 -04:00
Sam 65503a5038 FIX: various issues with llm and triage management (#1186)
- Fix search API to only include column_names when present to make the API less confusing
- Ensure correct LLM is used in PMs by tracking and preferring the last bot user
- Fix persona_id conversion from string to integer in custom fields
- Add missing test for PM triage with no replies - ensure we don't try to auto title topic 
- Ensure bot users are properly added to PMs
- Make title setting optional when replying to posts
- Add ability to control stream_reply behavior

These changes improve reliability and fix edge cases in bot interactions,
particularly in private messages with multiple LLMs and while triaging posts using personas
2025-03-12 18:32:02 +11:00
Keegan George b17c688162 DEV: Improve title suggester suggestions when editing topic (#1182)
This update ensures topic title suggestions when suggesting from edit topic take into account the whole topic for more accurate title suggestions.
2025-03-11 11:16:06 -07:00
Discourse Translator Bot e59184b65c Update translations (#1185) 2025-03-11 17:47:51 +01:00
Sam 8f4cd2fcbd FEATURE: allow disabling of top_p and temp for thinking models (#1184)
thinking models such as Claude 3.7 Thinking and o1 / o3 do not
support top_p or temp.

Previously you would have to carefully remove it from everywhere
by having it be a provider param we now support blanker removing
without forcing people to update automation rules or personas
2025-03-11 16:54:02 +11:00
Guhyoun Nam f4708d4178 DEV: Ai summary utilizing name instead of username (#1180)
* DEV: Ai summary utilizing name instead of username

* utilize username if name not available

* test added

* test added 2
2025-03-11 14:29:13 +11:00
dependabot[bot] 833f914e34 Build(deps-dev): Bump rack from 3.1.10 to 3.1.12 (#1181)
Bumps [rack](https://github.com/rack/rack) from 3.1.10 to 3.1.12.
- [Release notes](https://github.com/rack/rack/releases)
- [Changelog](https://github.com/rack/rack/blob/main/CHANGELOG.md)
- [Commits](https://github.com/rack/rack/compare/v3.1.10...v3.1.12)

---
updated-dependencies:
- dependency-name: rack
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <[email protected]>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-03-11 14:27:34 +11:00
Sam 168d9d8eb9 FIX: AI triage support and refactor search functionality (#1175)
* FIX: do not add bot user to PM when using responders

* Allow AI tool to call search directly

* remove stray p
2025-03-11 14:26:07 +11:00
Krzysztof Kotlarek 3da4f5eac3 DEV: fix Zeitwerk issue with SpamScanner class (#1183)
When spam scanner is enabled and code is reloaded, developer experience this error:

```
NameError at /posts
===================

uninitialized constant DiscourseAi::AiModeration::EntryPoint::SpamScanner

> To access an interactive console with this error, point your browser to: /__better_errors

plugins/discourse-ai/lib/ai_moderation/entry_point.rb, line 7
```

It is because when we call `SpamScanner` it is searched within parent `DiscourseAi::AiModeration::EntryPoint` namespace.

We can help a bit Zeitwerk by calling SpamScanner more explicitly.
2025-03-11 13:24:27 +11:00
Keegan George bb32d0d737 FEATURE: Add ability to disable search discoveries (#1177)
This update adds the ability to disable search discoveries. This can be done through a tooltip when search discoveries are shown. It can also be done in the AI user preferences, which has also been updated to accommodate more than just the one image caption setting.
2025-03-10 14:17:58 -07:00
Joffrey JAFFEUXandKeegan George 339251a371 FIX: prevents double escaping of filters (#1179)
---------

Co-authored-by: Keegan George <[email protected]>
2025-03-10 14:17:24 -07:00
157 changed files with 4249 additions and 2585 deletions
+59 -45
View File
@@ -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
@@ -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);
}
}
@@ -5,5 +5,6 @@
@presets={{this.presets}}
@llms={{this.llms}}
@settings={{this.settings}}
@selectedPreset={{this.selectedPreset}}
/>
</section>
@@ -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
+8
View File
@@ -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
@@ -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;
@@ -268,6 +268,7 @@ export default class AdminReportSentimentAnalysis extends Component {
this.router.transitionTo(this.router.currentRoute.name, {
queryParams: {
...currentQueryParams,
filters: JSON.parse(currentQueryParams.filters), // avoids a double escaping
selectedChart: null,
},
});
@@ -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
@@ -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>
}
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>
}
@@ -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() {
@@ -16,6 +16,7 @@ export default class ShareModal extends Component {
@service modal;
@service siteSettings;
@service currentUser;
@tracked contextValue = 1;
@tracked htmlContext = "";
@tracked maxContext = 0;
@@ -9,6 +9,7 @@ import { i18n } from "discourse-i18n";
export default class RagUploadProgress extends Component {
@service messageBus;
@tracked updatedProgress = null;
willDestroy() {
@@ -16,6 +16,7 @@ import RagUploadProgress from "./rag-upload-progress";
export default class RagUploader extends Component {
@service appEvents;
@tracked term = null;
@tracked filteredUploads = null;
@tracked ragIndexingStatuses = null;
@@ -136,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"}}
@@ -15,32 +15,19 @@ import { MIN_CHARACTER_COUNT } from "../../lib/ai-helper-suggestions";
export default class AiCategorySuggester extends Component {
@service siteSettings;
@tracked loading = false;
@tracked suggestions = null;
@tracked untriggers = [];
@tracked triggerIcon = "discourse-sparkles";
@tracked content = null;
@tracked topicContent = null;
constructor() {
super(...arguments);
if (!this.topicContent && this.args.composer?.reply === undefined) {
this.fetchTopicContent();
}
}
async fetchTopicContent() {
await ajax(`/t/${this.args.buffered.content.id}.json`).then(
({ post_stream }) => {
this.topicContent = post_stream.posts[0].cooked;
}
);
}
get showSuggestionButton() {
const composerFields = document.querySelector(".composer-fields");
this.content = this.args.composer?.reply || this.topicContent;
const showTrigger = this.content?.length > MIN_CHARACTER_COUNT;
this.content = this.args.composer?.reply;
const showTrigger =
this.content?.length > MIN_CHARACTER_COUNT ||
this.args.topicState === "edit";
if (composerFields) {
if (showTrigger) {
@@ -62,12 +49,20 @@ export default class AiCategorySuggester extends Component {
this.loading = true;
this.triggerIcon = "spinner";
const data = {};
if (this.content) {
data.text = this.content;
} else {
data.topic_id = this.args.buffered.content.id;
}
try {
const { assistant } = await ajax(
"/discourse-ai/ai-helper/suggest_category",
{
method: "POST",
data: { text: this.content },
data,
}
);
this.suggestions = assistant;
@@ -16,32 +16,19 @@ import { MIN_CHARACTER_COUNT } from "../../lib/ai-helper-suggestions";
export default class AiTagSuggester extends Component {
@service siteSettings;
@service toasts;
@tracked loading = false;
@tracked suggestions = null;
@tracked untriggers = [];
@tracked triggerIcon = "discourse-sparkles";
@tracked content = null;
@tracked topicContent = null;
constructor() {
super(...arguments);
if (!this.topicContent && this.args.composer?.reply === undefined) {
this.fetchTopicContent();
}
}
async fetchTopicContent() {
await ajax(`/t/${this.args.buffered.content.id}.json`).then(
({ post_stream }) => {
this.topicContent = post_stream.posts[0].cooked;
}
);
}
get showSuggestionButton() {
const composerFields = document.querySelector(".composer-fields");
this.content = this.args.composer?.reply || this.topicContent;
const showTrigger = this.content?.length > MIN_CHARACTER_COUNT;
this.content = this.args.composer?.reply;
const showTrigger =
this.content?.length > MIN_CHARACTER_COUNT ||
this.args.topicState === "edit";
if (composerFields) {
if (showTrigger) {
@@ -74,15 +61,25 @@ export default class AiTagSuggester extends Component {
this.loading = true;
this.triggerIcon = "spinner";
const data = {};
if (this.content) {
data.text = this.content;
} else {
data.topic_id = this.args.buffered.content.id;
}
try {
const { assistant } = await ajax("/discourse-ai/ai-helper/suggest_tags", {
method: "POST",
data: { text: this.content },
data,
});
this.suggestions = assistant;
const model = this.args.composer
? this.args.composer
: this.args.buffered;
if (this.#tagSelectorHasValues()) {
this.suggestions = this.suggestions.filter(
(s) => !model.get("tags").includes(s.name)
@@ -17,30 +17,15 @@ export default class AiTitleSuggester extends Component {
@tracked untriggers = [];
@tracked triggerIcon = "discourse-sparkles";
@tracked content = null;
@tracked topicContent = null;
constructor() {
super(...arguments);
if (!this.topicContent && this.args.composer?.reply === undefined) {
this.fetchTopicContent();
}
}
async fetchTopicContent() {
await ajax(`/t/${this.args.buffered.content.id}.json`).then(
({ post_stream }) => {
this.topicContent = post_stream.posts[0].cooked;
}
);
}
get showSuggestionButton() {
const composerFields = document.querySelector(".composer-fields");
const editTopicTitleField = document.querySelector(".edit-topic-title");
this.content = this.args.composer?.reply || this.topicContent;
const showTrigger = this.content?.length > MIN_CHARACTER_COUNT;
this.content = this.args.composer?.reply;
const showTrigger =
this.content?.length > MIN_CHARACTER_COUNT ||
this.args.topicState === "edit";
if (composerFields) {
if (showTrigger) {
@@ -69,13 +54,20 @@ export default class AiTitleSuggester extends Component {
this.loading = true;
this.triggerIcon = "spinner";
const data = {};
if (this.content) {
data.text = this.content;
} else {
data.topic_id = this.args.buffered.content.id;
}
try {
const { suggestions } = await ajax(
"/discourse-ai/ai-helper/suggest_title",
{
method: "POST",
data: { text: this.content },
data,
}
);
this.suggestions = suggestions;
@@ -13,6 +13,6 @@ export default class AiCategorySuggestion extends Component {
}
<template>
<AiCategorySuggester @composer={{@outletArgs.composer}} />
<AiCategorySuggester @composer={{@outletArgs.composer}} @topicState="new" />
</template>
}
@@ -13,6 +13,6 @@ export default class AiTagSuggestion extends Component {
}
<template>
<AiTagSuggester @composer={{@outletArgs.composer}} />
<AiTagSuggester @composer={{@outletArgs.composer}} @topicState="new" />
</template>
}
@@ -13,6 +13,6 @@ export default class AiTitleSuggestion extends Component {
}
<template>
<AiTitleSuggester @composer={{@outletArgs.composer}} />
<AiTitleSuggester @composer={{@outletArgs.composer}} @topicState="new" />
</template>
}
@@ -9,7 +9,5 @@ export default class AiTopicGist extends Component {
return !this.topicThumbnails?.enabledForRoute;
}
<template>
<AiGistToggle />
</template>
<template><AiGistToggle /></template>
}
@@ -31,6 +31,7 @@ export default class BotSelector extends Component {
@service currentUser;
@service siteSettings;
@tracked llm;
@tracked allowLLMSelector = true;
@@ -13,6 +13,9 @@ export default class AiCategorySuggestion extends Component {
}
<template>
<AiCategorySuggester @buffered={{@outletArgs.buffered}} />
<AiCategorySuggester
@buffered={{@outletArgs.buffered}}
@topicState="edit"
/>
</template>
}

Some files were not shown because too many files have changed in this diff Show More