Compare commits

...
Author SHA1 Message Date
Roman Rizzi 822594f3e3 Update db/migrate/20250716005855_copy_ai_image_caption_model_to_persona_default.rb 2025-07-21 18:00:01 -03:00
Keegan George 7dee59c97f DEV: Remove no longer needed seeded model check 2025-07-21 08:08:20 -07:00
Keegan George fbbe01e576 DEV: Hide seeded LLMs in the enumerator for now 2025-07-21 07:45:27 -07:00
Keegan George f420261196 DEV: Apply feedback from review 2025-07-18 12:17:08 -07:00
Keegan George eb93e1736b Final spec fix 🤞 2025-07-17 14:46:15 -07:00
Keegan George cc34882074 FIX: rest of specs 2025-07-17 14:34:29 -07:00
Keegan George 4b03cdcc96 FIX: LLM controller spec 2025-07-17 13:31:26 -07:00
Keegan George d9b53b4a3d FIX: semantic search 2025-07-17 12:23:38 -07:00
Keegan George a927fd2270 fix 2025-07-17 12:00:05 -07:00
Keegan George f077d58abf FIX: automation spec 2025-07-17 11:48:28 -07:00
Keegan George 08d2f3ddf9 FIX: spam 2025-07-17 11:36:19 -07:00
Keegan George 273a1fa618 FIX: occurrences of old model setting 2025-07-17 09:43:45 -07:00
Keegan George 7af3ce820d DEV: Remove references to translation model 2025-07-17 09:27:08 -07:00
Keegan George 5841543bd9 FIX: validator 2025-07-16 14:51:32 -07:00
Keegan George 5ae80645bb Merge branch 'main' into default-llm-model 2025-07-16 12:07:10 -07:00
Keegan George b675c4c39b DEV: Remove custom prefix in specs 2025-07-16 11:39:55 -07:00
Keegan George 0fadf1da1a DEV: update enumerator 2025-07-16 10:56:18 -07:00
Keegan George c26d604072 FIX: dependency validator should depend on default LLM setting 2025-07-16 07:08:29 -07:00
Natalie Tay ad6a8cb812 DEV: Switch translations out of structured output as it returns only a single value (#1503)
Since translations only require a single key back, there is little point in using structured output. This PR also includes some prompt updates dealing with quotes, details, and code.

Related: #1502

This does mean reverting discourse/discourse-translator#257, but we can see how it goes.
2025-07-16 13:38:00 +08:00
Keegan George 7ae61ce877 DEV: update automation to remove custom: prefix 2025-07-15 18:08:41 -07:00
Keegan George f593ab6e1e DEV: remove custom: prefix
and update migrations for translations
2025-07-15 18:07:05 -07:00
Keegan George b58f198ee8 FIX: don't run_test in testing env 2025-07-15 11:08:39 -07:00
Roman Rizzi 06743d1939 FIX: Fix embeddings to use the old OpenAI tokenizer (#1506) 2025-07-15 14:44:11 -03:00
Keegan George 88811f97bf fixes 2025-07-15 10:23:58 -07:00
Keegan George 2377b286dd DEV: rely on default llm model in spec 2025-07-15 09:56:24 -07:00
Kris 67664029e5 FIX: prevent crash in "all" filter on features page (#1505)
* FIX: prevent crash in "all" filter on features page

* simplify
2025-07-15 12:51:46 -04:00
Sam 1b16fc876c FIX: avoid using structured outputs for report runs (#1502)
Structured outputs are prone to formatting issue, especially around newlines
and custom pieces of text that need escaping.

This avoids using it for the automation reporting.

Particularly previous to this fix o4-mini based reports were broken
2025-07-15 13:14:52 +10:00
Discourse Translator Bot 8c069490f0 Update translations (#1498) 2025-07-15 07:28:15 +10:00
Roman Rizzi 6a4dbd8126 FEATURE: Use Personas in Automation's llm_report script (#1499) 2025-07-14 12:47:21 -03:00
Keegan George 8da4318021 DEV: assign_fake_provider 2025-07-11 12:32:36 -07:00
Keegan George b6a88ff03e spec 2025-07-11 12:02:50 -07:00
Keegan George f366ded03b DEV: Force default to be set if it was found not to be set! 2025-07-11 08:19:30 -07:00
Keegan George 87c42145d4 DEV: hide hyde model setting 2025-07-10 15:07:12 -07:00
Keegan George c49f1df2ba DEV: migrate hyde model to content creator persona 2025-07-10 15:00:34 -07:00
Keegan George b46940fc66 DEV: hide reset button
It resets it to default before the migration
2025-07-10 14:46:02 -07:00
Keegan George 249aab100c DEV: Use a simple validator
- no need for seeding checks or module dependant checks
2025-07-10 14:44:02 -07:00
Roman Rizzi 89bcf9b1f0 FIX: Process succesfully generated embeddings even if some failed (#1500) 2025-07-10 17:51:01 -03:00
Keegan George 5a29074799 DEV: Use default LLM model 2025-07-10 11:54:35 -07:00
Martin Brennan 6b5ea38644 DEV: Improve AI bot conversation submit upload handling (#1497)
Try fix a flaky spec in /ai_bot/homepage_spec.rb by using ember
data rather than inspecting the DOM directly to see if there
are any in-progress uploads.

Also add missing translation for in progress uploads warning.
2025-07-10 17:06:39 +10:00
Keegan George 29d415755a fix 2025-07-09 15:42:44 -07:00
Keegan George 5a590fddc1 DEV: updates... 2025-07-09 15:36:34 -07:00
Keegan George cad39c1aa4 DEV: client side 2025-07-09 15:29:50 -07:00
Keegan George 2592be65ff FEATURE: configure a default LLM model for all features
This update adds a setting to the configure a default LLM model to be used for all features (unless overridden).
2025-07-09 15:29:24 -07:00
Natalie Tay d54cd1f602 DEV: Normalize locales that are similar (e.g. en and en_GB) so they do not get translated (#1495)
This commit
- normalizes locales like en_GB and variants to en. With this, the feature will not translate en_GB posts to en (or similarly pt_BR to pt_PT)
- consolidates whether the feature is enabled in `DiscourseAi::Translation.enabled?`
- similarly for backfill in  `DiscourseAi::Translation.backfill_enabled?`
  - turns off backfill if `ai_translation_backfill_max_age_days` is 0 to keep true to what it says. Set it to a high number to backfill everything
2025-07-09 22:21:51 +08:00
Keegan George cf7288e1bf DEV: Surface topic_id in errors of summarization backfill (#1493)
This update ensures that the `topic_id` related to the error when summarizing is surfaced in the logs, which should help track down the reason for the errors.
2025-07-09 06:39:36 -07:00
Discourse Translator Bot 027d7f1199 Update translations (#1492) 2025-07-09 15:27:19 +02:00
173 changed files with 1445 additions and 782 deletions
+5 -4
View File
@@ -6,8 +6,7 @@ module Jobs
sidekiq_options retry: false
def execute(args)
return if !SiteSetting.discourse_ai_enabled
return if !SiteSetting.ai_translation_enabled
return if !DiscourseAi::Translation.enabled?
return if args[:post_id].blank?
post = Post.find_by(id: args[:post_id])
@@ -36,12 +35,14 @@ module Jobs
end
end
return if detected_locale.blank?
locales = SiteSetting.content_localization_supported_locales.split("|")
return if locales.blank?
locales.each do |locale|
next if locale == detected_locale
next if post.post_localizations.exists?(locale:)
next if DiscourseAi::Translation::LocaleNormalizer.is_same?(locale, detected_locale)
regionless_locale = locale.split("_").first
next if post.post_localizations.where("locale LIKE ?", "#{regionless_locale}%").exists?
begin
DiscourseAi::Translation::PostLocalizer.localize(post, locale)
+5 -4
View File
@@ -6,8 +6,7 @@ module Jobs
sidekiq_options retry: false
def execute(args)
return if !SiteSetting.discourse_ai_enabled
return if !SiteSetting.ai_translation_enabled
return if !DiscourseAi::Translation.enabled?
return if args[:topic_id].blank?
topic = Topic.find_by(id: args[:topic_id])
@@ -34,12 +33,14 @@ module Jobs
end
end
return if detected_locale.blank?
locales = SiteSetting.content_localization_supported_locales.split("|")
return if locales.blank?
locales.each do |locale|
next if locale == detected_locale
next if topic.topic_localizations.exists?(locale:)
next if DiscourseAi::Translation::LocaleNormalizer.is_same?(locale, detected_locale)
regionless_locale = locale.split("_").first
next if topic.topic_localizations.where("locale LIKE ?", "#{regionless_locale}%").exists?
begin
DiscourseAi::Translation::TopicLocalizer.localize(topic, locale)
+3 -6
View File
@@ -6,21 +6,17 @@ module Jobs
sidekiq_options retry: false
def execute(args)
return if !DiscourseAi::Translation.enabled?
limit = args[:limit]
raise Discourse::InvalidParameters.new(:limit) if limit.nil?
return if limit <= 0
return if !SiteSetting.discourse_ai_enabled
return if !SiteSetting.ai_translation_enabled
locales = SiteSetting.content_localization_supported_locales.split("|")
return if locales.blank?
categories = Category.where("locale IS NOT NULL")
if SiteSetting.ai_translation_backfill_limit_to_public_content
categories = categories.where(read_restricted: false)
end
categories = categories.order(:id).limit(limit)
return if categories.empty?
@@ -33,6 +29,7 @@ module Jobs
missing_locales = locales - existing_locales - [category.locale]
missing_locales.each do |locale|
break if remaining_limit <= 0
next if DiscourseAi::Translation::LocaleNormalizer.is_same?(locale, category.locale)
begin
DiscourseAi::Translation::CategoryLocalizer.localize(category, locale)
+8 -14
View File
@@ -9,23 +9,25 @@ module Jobs
limit = args[:limit]
raise Discourse::InvalidParameters.new(:limit) if limit.blank? || limit <= 0
return if !SiteSetting.discourse_ai_enabled
return if !SiteSetting.ai_translation_enabled
return if !DiscourseAi::Translation.backfill_enabled?
locales = SiteSetting.content_localization_supported_locales.split("|")
return if locales.blank?
locales.each do |locale|
base_locale = locale.split("_").first
posts =
Post
.joins(
"LEFT JOIN post_localizations pl ON pl.post_id = posts.id AND pl.locale = #{ActiveRecord::Base.connection.quote(locale)}",
"LEFT JOIN post_localizations pl ON pl.post_id = posts.id AND pl.locale LIKE '#{base_locale}%'",
)
.where(
"posts.created_at > ?",
SiteSetting.ai_translation_backfill_max_age_days.days.ago,
)
.where(deleted_at: nil)
.where("posts.user_id > 0")
.where.not(raw: [nil, ""])
.where.not(locale: nil)
.where.not(locale: locale)
.where("posts.locale NOT LIKE '#{base_locale}%'")
.where("pl.id IS NULL")
posts = posts.joins(:topic)
@@ -46,14 +48,6 @@ module Jobs
)
end
if SiteSetting.ai_translation_backfill_max_age_days > 0
posts =
posts.where(
"posts.created_at > ?",
SiteSetting.ai_translation_backfill_max_age_days.days.ago,
)
end
posts = posts.order(updated_at: :desc).limit(limit)
next if posts.empty?
+8 -14
View File
@@ -9,22 +9,24 @@ module Jobs
limit = args[:limit]
raise Discourse::InvalidParameters.new(:limit) if limit.blank? || limit <= 0
return if !SiteSetting.discourse_ai_enabled
return if !SiteSetting.ai_translation_enabled
return if !DiscourseAi::Translation.backfill_enabled?
locales = SiteSetting.content_localization_supported_locales.split("|")
return if locales.blank?
locales.each do |locale|
base_locale = locale.split("_").first
topics =
Topic
.joins(
"LEFT JOIN topic_localizations tl ON tl.topic_id = topics.id AND tl.locale = #{ActiveRecord::Base.connection.quote(locale)}",
"LEFT JOIN topic_localizations tl ON tl.topic_id = topics.id AND tl.locale LIKE '#{base_locale}%'",
)
.where(
"topics.created_at > ?",
SiteSetting.ai_translation_backfill_max_age_days.days.ago,
)
.where(deleted_at: nil)
.where("topics.user_id > 0")
.where.not(locale: nil)
.where.not(locale: locale)
.where("topics.locale NOT LIKE '#{base_locale}%'")
.where("tl.id IS NULL")
if SiteSetting.ai_translation_backfill_limit_to_public_content
@@ -43,14 +45,6 @@ module Jobs
)
end
if SiteSetting.ai_translation_backfill_max_age_days > 0
topics =
topics.where(
"topics.created_at > ?",
SiteSetting.ai_translation_backfill_max_age_days.days.ago,
)
end
topics = topics.order(updated_at: :desc).limit(limit)
next if topics.empty?
@@ -7,10 +7,7 @@ module Jobs
cluster_concurrency 1
def execute(args)
return if !SiteSetting.discourse_ai_enabled
return if !SiteSetting.ai_translation_enabled
limit = SiteSetting.ai_translation_backfill_hourly_rate
return if limit == 0
return if !DiscourseAi::Translation.backfill_enabled?
categories = Category.where(locale: nil)
@@ -18,6 +15,7 @@ module Jobs
categories = categories.where(read_restricted: false)
end
limit = SiteSetting.ai_translation_backfill_hourly_rate
categories = categories.limit(limit)
return if categories.empty?
@@ -6,11 +6,8 @@ module Jobs
cluster_concurrency 1
def execute(args)
return if !SiteSetting.discourse_ai_enabled
return if !SiteSetting.ai_translation_enabled
return if SiteSetting.content_localization_supported_locales.blank?
return if !DiscourseAi::Translation.backfill_enabled?
limit = SiteSetting.ai_translation_backfill_hourly_rate
return if limit == 0
Jobs.enqueue(:localize_categories, limit:)
end
@@ -6,10 +6,8 @@ module Jobs
cluster_concurrency 1
def execute(args)
return if !SiteSetting.discourse_ai_enabled
return if !SiteSetting.ai_translation_enabled
return if !DiscourseAi::Translation.backfill_enabled?
return if SiteSetting.content_localization_supported_locales.blank?
limit = SiteSetting.ai_translation_backfill_hourly_rate / (60 / 5) # this job runs in 5-minute intervals
return if limit == 0
@@ -7,16 +7,16 @@ module Jobs
cluster_concurrency 1
def execute(args)
return if !SiteSetting.discourse_ai_enabled
return if !SiteSetting.ai_translation_enabled
return if !DiscourseAi::Translation.backfill_enabled?
limit = SiteSetting.ai_translation_backfill_hourly_rate / (60 / 5) # this job runs in 5-minute intervals
return if limit == 0
posts =
Post
.where(locale: nil)
.where(deleted_at: nil)
.where("posts.user_id > 0")
.where("posts.created_at > ?", SiteSetting.ai_translation_backfill_max_age_days.days.ago)
.where.not(raw: [nil, ""])
if SiteSetting.ai_translation_backfill_limit_to_public_content
@@ -33,14 +33,6 @@ module Jobs
)
end
if SiteSetting.ai_translation_backfill_max_age_days > 0
posts =
posts.where(
"posts.created_at > ?",
SiteSetting.ai_translation_backfill_max_age_days.days.ago,
)
end
posts = posts.order(updated_at: :desc).limit(limit)
return if posts.empty?
+6 -1
View File
@@ -36,7 +36,12 @@ module ::Jobs
existing_summary = strategy.existing_summary
if existing_summary.blank? || existing_summary.outdated
strategy.summarize(user)
begin
strategy.summarize(user)
rescue => e
Rails.logger.error("Error summarizing topic #{topic.id}: #{e.class.name} - #{e.message}")
raise e
end
else
# Hiding or deleting a post, and creating a small action alters the Topic#highest_post_number.
# We use this as a quick way to select potential backfill candidates without relying on original_content_sha.
@@ -6,13 +6,9 @@ module Jobs
cluster_concurrency 1
def execute(args)
return if !SiteSetting.discourse_ai_enabled
return if !SiteSetting.ai_translation_enabled
return if !DiscourseAi::Translation.backfill_enabled?
return if SiteSetting.content_localization_supported_locales.blank?
limit = SiteSetting.ai_translation_backfill_hourly_rate / (60 / 5) # this job runs in 5-minute intervals
return if limit == 0
Jobs.enqueue(:localize_topics, limit:)
end
end
@@ -7,12 +7,15 @@ module Jobs
cluster_concurrency 1
def execute(args)
return if !SiteSetting.discourse_ai_enabled
return if !SiteSetting.ai_translation_enabled
limit = SiteSetting.ai_translation_backfill_hourly_rate / (60 / 5) # this job runs in 5-minute intervals
return if limit == 0
return if !DiscourseAi::Translation.backfill_enabled?
topics = Topic.where(locale: nil, deleted_at: nil).where("topics.user_id > 0")
limit = SiteSetting.ai_translation_backfill_hourly_rate / (60 / 5) # this job runs in 5-minute intervals
topics =
Topic
.where(locale: nil, deleted_at: nil)
.where("topics.user_id > 0")
.where("topics.created_at > ?", SiteSetting.ai_translation_backfill_max_age_days.days.ago)
if SiteSetting.ai_translation_backfill_limit_to_public_content
topics =
@@ -28,14 +31,6 @@ module Jobs
)
end
if SiteSetting.ai_translation_backfill_max_age_days > 0
topics =
topics.where(
"topics.created_at > ?",
SiteSetting.ai_translation_backfill_max_age_days.days.ago,
)
end
topics = topics.order(updated_at: :desc).limit(limit)
return if topics.empty?
-12
View File
@@ -12,7 +12,6 @@ class AiPersona < ActiveRecord::Base
validates :system_prompt, presence: true, length: { maximum: 10_000_000 }
validate :system_persona_unchangeable, on: :update, if: :system
validate :chat_preconditions
validate :allowed_seeded_model, if: :default_llm_id
validate :well_formated_examples
validates :max_context_posts, numericality: { greater_than: 0 }, allow_nil: true
# leaves some room for growth but sets a maximum to avoid memory issues
@@ -365,17 +364,6 @@ class AiPersona < ActiveRecord::Base
end
end
def allowed_seeded_model
return if default_llm_id.blank?
return if default_llm.nil?
return if !default_llm.seeded?
return if SiteSetting.ai_bot_allowed_seeded_models_map.include?(default_llm.id.to_s)
errors.add(:default_llm, I18n.t("discourse_ai.llm.configuration.invalid_seeded_model"))
end
def well_formated_examples
return if examples.blank?
+3 -3
View File
@@ -84,7 +84,7 @@ class EmbeddingDefinition < ActiveRecord::Base
dimensions: 2000,
max_sequence_length: 8191,
pg_function: "<=>",
tokenizer_class: "DiscourseAi::Tokenizer::OpenAiTokenizer",
tokenizer_class: "DiscourseAi::Tokenizer::OpenAiCl100kTokenizer",
url: "https://api.openai.com/v1/embeddings",
provider: OPEN_AI,
matryoshka_dimensions: true,
@@ -98,7 +98,7 @@ class EmbeddingDefinition < ActiveRecord::Base
dimensions: 1536,
max_sequence_length: 8191,
pg_function: "<=>",
tokenizer_class: "DiscourseAi::Tokenizer::OpenAiTokenizer",
tokenizer_class: "DiscourseAi::Tokenizer::OpenAiCl100kTokenizer",
url: "https://api.openai.com/v1/embeddings",
provider: OPEN_AI,
matryoshka_dimensions: true,
@@ -112,7 +112,7 @@ class EmbeddingDefinition < ActiveRecord::Base
dimensions: 1536,
max_sequence_length: 8191,
pg_function: "<=>",
tokenizer_class: "DiscourseAi::Tokenizer::OpenAiTokenizer",
tokenizer_class: "DiscourseAi::Tokenizer::OpenAiCl100kTokenizer",
url: "https://api.openai.com/v1/embeddings",
provider: OPEN_AI,
provider_params: {
+1 -1
View File
@@ -99,7 +99,7 @@ class LlmModel < ActiveRecord::Base
end
def identifier
"custom:#{id}"
"#{id}"
end
def toggle_companion_user
@@ -260,10 +260,11 @@ export default class AiBotConversations extends Component {
@action
async prepareAndSubmitToBot() {
// Pass uploads to the service before submitting
this.aiBotConversationsHiddenSubmit.uploads = this.uploads;
try {
await this.aiBotConversationsHiddenSubmit.submitToBot();
await this.aiBotConversationsHiddenSubmit.submitToBot({
uploads: this.uploads,
inProgressUploadsCount: this.inProgressUploads.length,
});
this.uploads = new TrackedArray();
} catch (error) {
popupAjaxError(error);
@@ -0,0 +1,46 @@
import Component from "@glimmer/component";
import { tracked } from "@glimmer/tracking";
import { ajax } from "discourse/lib/ajax";
import { i18n } from "discourse-i18n";
import SiteSettingComponent from "admin/components/site-setting";
import SiteSetting from "admin/models/site-setting";
export default class AiDefaultLlmSelector extends Component {
@tracked defaultLlmSetting = null;
constructor() {
super(...arguments);
this.#loadDefaultLlmSetting();
}
async #loadDefaultLlmSetting() {
const { site_settings } = await ajax("/admin/config/site_settings.json", {
data: {
plugin: "discourse-ai",
category: "discourse_ai",
},
});
const defaultLlmSetting = site_settings.find(
(setting) => setting.setting === "ai_default_llm_model"
);
this.defaultLlmSetting = SiteSetting.create(defaultLlmSetting);
}
<template>
<div class="ai-configure-default-llm">
<div class="ai-configure-default-llm__header">
<h3>{{i18n "discourse_ai.default_llm.title"}}</h3>
<p>{{i18n "discourse_ai.default_llm.description"}}</p>
</div>
{{#if this.defaultLlmSetting}}
<SiteSettingComponent
@setting={{this.defaultLlmSetting}}
class="ai-configure-default-llm__setting"
/>
{{/if}}
</div>
</template>
}
@@ -66,12 +66,9 @@ class ExpandableList extends Component {
export default class AiFeaturesList extends Component {
get sortedModules() {
if (!this.args.modules || !this.args.modules.length) {
return [];
}
return this.args.modules.toSorted((a, b) =>
a.module_name.localeCompare(b.module_name)
const modules = this.args.modules?.toArray() || this.args.modules;
return [...(modules || [])].toSorted((a, b) =>
(a.module_name || "").localeCompare(b.module_name || "")
);
}
@@ -9,6 +9,7 @@ import DPageSubheader from "discourse/components/d-page-subheader";
import DSelect from "discourse/components/d-select";
import FilterInput from "discourse/components/filter-input";
import { i18n } from "discourse-i18n";
import AiDefaultLlmSelector from "./ai-default-llm-selector";
import AiFeaturesList from "./ai-features-list";
const ALL = "all";
@@ -202,6 +203,8 @@ export default class AiFeatures extends Component {
/>
</div>
<AiDefaultLlmSelector />
{{#if this.filteredFeatures.length}}
<AiFeaturesList @modules={{this.filteredFeatures}} />
{{else}}
@@ -9,6 +9,7 @@ import I18n, { i18n } from "discourse-i18n";
import AdminSectionLandingItem from "admin/components/admin-section-landing-item";
import AdminSectionLandingWrapper from "admin/components/admin-section-landing-wrapper";
import DTooltip from "float-kit/components/d-tooltip";
import AiDefaultLlmSelector from "./ai-default-llm-selector";
import AiLlmEditor from "./ai-llm-editor";
function isPreseeded(llm) {
@@ -137,6 +138,9 @@ export default class AiLlmsListEditor extends Component {
}}
@learnMoreUrl="https://meta.discourse.org/t/discourse-ai-large-language-model-llm-settings-page/319903"
/>
<AiDefaultLlmSelector />
{{#if this.hasLlmElements}}
<section class="ai-llms-list-editor__configured">
<DPageSubheader
@@ -90,7 +90,7 @@ export default class AiSpam extends Component {
this.isEnabled = model.is_enabled;
if (model.llm_id) {
this.selectedLLM = "custom:" + model.llm_id;
this.selectedLLM = model.llm_id;
} else {
if (this.availableLLMs.length) {
this.selectedLLM = this.availableLLMs[0].id;
@@ -127,7 +127,7 @@ export default class AiSpam extends Component {
}
get llmId() {
return this.selectedLLM.toString().split(":")[1];
return this.selectedLLM;
}
@action
@@ -18,7 +18,6 @@ export default class AiBotConversationsHiddenSubmit extends Service {
personaId;
targetUsername;
uploads = [];
inputValue = "";
@@ -32,7 +31,7 @@ export default class AiBotConversationsHiddenSubmit extends Service {
}
@action
async submitToBot() {
async submitToBot(uploadData) {
if (
this.inputValue.length <
this.siteSettings.min_personal_message_post_length
@@ -48,7 +47,7 @@ export default class AiBotConversationsHiddenSubmit extends Service {
}
// Don't submit if there are still uploads in progress
if (document.querySelector(".ai-bot-upload--in-progress")) {
if (uploadData.inProgressUploadsCount > 0) {
return this.dialog.alert({
message: i18n("discourse_ai.ai_bot.conversations.uploads_in_progress"),
});
@@ -61,10 +60,10 @@ export default class AiBotConversationsHiddenSubmit extends Service {
let rawContent = this.inputValue;
// Append upload markdown if we have uploads
if (this.uploads && this.uploads.length > 0) {
if (uploadData.uploads && uploadData.uploads.length > 0) {
rawContent += "\n\n";
this.uploads.forEach((upload) => {
uploadData.uploads.forEach((upload) => {
const uploadMarkdown = getUploadMarkdown(upload);
rawContent += uploadMarkdown + "\n";
});
@@ -83,7 +82,6 @@ export default class AiBotConversationsHiddenSubmit extends Service {
});
// Reset uploads after successful submission
this.uploads = [];
this.inputValue = "";
this.appEvents.trigger("discourse-ai:bot-pm-created", {
@@ -158,3 +158,51 @@
.ai-expanded-list__toggle-button {
padding: 0;
}
.ai-configure-default-llm {
display: flex;
align-items: center;
gap: var(--space-2);
background: var(--primary-very-low);
padding: 1rem;
margin-block: 1rem;
border-radius: var(--d-border-radius);
&__header {
flex: 3;
}
&__setting {
flex: 2;
display: flex;
gap: var(--space-2);
align-items: center;
justify-content: center;
.setting-controls__undo {
display: none;
}
}
h3 {
color: var(--primary);
font-size: var(--font-0);
margin-bottom: 0;
}
p {
margin-top: 0;
font-size: var(--font-down-1);
color: var(--primary-high-or-secondary-low);
}
.select-kit,
.btn {
font-size: var(--font-down-1);
}
.setting-label,
.desc {
display: none;
}
}
+2
View File
@@ -158,6 +158,8 @@ ar:
reset: "إعادة التعيين"
search:
name: "البحث"
embeddings:
name: "التضمينات"
ai_helper:
name: "المساعد"
proofread: تدقيق النص لغويًا
+85 -8
View File
@@ -29,6 +29,22 @@ de:
label: "Sortieren nach"
tag:
label: "Schlagwort"
logs:
staff_actions:
actions:
create_ai_llm_model: "LLM-Modell erstellen"
update_ai_llm_model: "LLM-Modell aktualisieren"
delete_ai_llm_model: "LLM-Modell löschen"
create_ai_persona: "KI-Persona erstellen"
update_ai_persona: "KI-Persona aktualisieren"
delete_ai_persona: "KI-Persona löschen"
create_ai_tool: "KI-Tool erstellen"
update_ai_tool: "KI-Tool aktualisieren"
delete_ai_tool: "KI-Tool löschen"
create_ai_embedding: "KI-Einbettung erstellen"
update_ai_embedding: "KI-Einbettung aktualisieren"
delete_ai_embedding: "KI-Einbettung löschen"
update_ai_spam_settings: "KI-Spam-Einstellungen aktualisieren"
js:
discourse_automation:
scriptables:
@@ -98,21 +114,21 @@ de:
fields:
model:
label: "Modell"
description: "Das für die Triage verwendete Standard-Sprachmodell"
description: "Das für die Sichtung verwendete Standard-Sprachmodell"
tool:
label: "Werkzeug"
description: "Werkzeug für die Triage (das Werkzeug darf keine Parameter definiert haben)"
label: "Tool"
description: "Tool für die Sichtung (das Tool darf keine Parameter definiert haben)"
llm_persona_triage:
fields:
persona:
label: "Persona"
description: "KI-Persona, die für die Triage verwendet werden soll (Standard-LLM und -Benutzer müssen eingestellt sein)"
description: "KI-Persona, die für die Sichtung verwendet werden soll (Standard-LLM und -Benutzer müssen eingestellt sein)"
whisper:
label: "Als Flüstern antworten"
description: "Ob die Antwort der Persona ein Flüstern sein soll"
silent_mode:
label: "Stiller Modus"
description: "Im stillen Modus empfängt die Persona den Inhalt, schreibt aber nichts in das Forum - nützlich bei der Triage mit Tools"
description: "Im stillen Modus empfängt die Persona den Inhalt, schreibt aber nichts in das Forum - nützlich bei der Sichtung mit Tools"
llm_triage:
fields:
system_prompt:
@@ -177,6 +193,7 @@ de:
llm:
one: "LLM:"
other: "LLM:"
no_llm: "Kein LLM ausgewählt"
no_persona: "Nicht festgelegt"
no_groups: "Keiner"
edit: "Bearbeiten"
@@ -184,35 +201,75 @@ de:
one: "(%{count} weitere)"
other: "(%{count} weitere)"
collapse_list: "(weniger anzeigen)"
bot:
bot: "Chatbot"
name: "Bot"
description: "Ein Chatbot, der Fragen beantworten und Benutzern in persönlichen Nachrichten, im Forum und im Chat helfen kann"
nav:
configured: "Konfiguriert"
unconfigured: "Nicht konfiguriert"
filters:
all: "Gesamt"
text: "Suche nach Funktionen, Personas, LLMs oder Gruppen..."
no_results: "Es wurden keine Funktionen gefunden, die deinen Filtern entsprechen."
reset: "Zurücksetzen"
summarization:
name: "Zusammenfassungen"
description: "Stellt eine Schaltfläche für Zusammenfassungen zur Verfügung, mit der Besucher Themen zusammenfassen können"
topic_summaries: "Themenzusammenfassungen"
gists: "Kurze Zusammenfassungen der in Themenliste"
search:
name: "Suche"
description: "Verbessert das Sucherlebnis durch KI-generierte Antworten auf Suchanfragen"
discoveries: "Entdeckungen"
embeddings:
name: "Einbettungen"
description: "Ermöglicht Funktionen wie „Verwandte Themen“ und „KI-Suche“ durch die Erstellung semantischer Textdarstellungen"
hyde: "HyDE"
discord:
name: "Discord-Integration"
description: "Fügt die Möglichkeit hinzu, Discord-Kanäle zu durchsuchen"
search: "Discord-Suche"
inference:
name: "Abgeleitete Konzepte"
description: "Ordnet Themen und Beiträge in Interessensgebiete / Labels."
generate_concepts: "Konzepterkennung"
match_concepts: "Passende Konzepte"
deduplicate_concepts: "Deduplizierung von Konzepten"
ai_helper:
name: "Helfer"
description: "Unterstützt Benutzer bei der Interaktion in der Community, z. B. beim Erstellen von Themen, Verfassen von Beiträgen und Lesen von Inhalten"
proofread: Text korrekturlesen
title_suggestions: "Titel vorschlagen"
explain: "Erklären"
illustrate_post: "Beitrag illustrieren"
smart_dates: "Intelligente Termine"
translate: "Übersetzen"
markdown_tables: "Markdown-Tabelle generieren"
custom_prompt: "Benutzerdefinierte Eingabeaufforderung"
image_caption: "Bildbeschriftungen"
translator: "Übersetzer"
translation:
name: "Übersetzen"
description: "Übersetzt Inhalte in unterstützte Sprachen"
locale_detector: "Sprachdetektor"
post_raw_translator: "Übersetzer für die Roheingabe des Beitrags"
topic_title_translator: "Thementitel-Übersetzer"
short_text_translator: "Kurztext-Übersetzer"
spam:
name: "Spam"
description: "Identifiziert potenziellen Spam mithilfe des ausgewählten LLMs und meldet ihn den Moderatoren der Website in der Warteschlange zur Überprüfung."
inspect_posts: "Beiträge prüfen"
modals:
select_option: "Wähle eine Option aus..."
layout:
table: "Tabelle"
card: "Karte"
spam:
short_title: "Spam"
title: "Spam-Behandlung konfigurieren"
select_llm: "LLM auswählen"
select_persona: "Persona auswählen"
custom_instructions: "Benutzerdefinierte Anweisungen"
custom_instructions_help: "Benutzerdefinierte Anweisungen speziell für deine Website, die der KI beim Identifizieren von Spam helfen, z. B. „Gehe beim Scannen von Posts, die nicht in englischer Sprache sind, aggressiver vor.“"
last_seven_days: "Letzte 7 Tage"
@@ -293,6 +350,9 @@ de:
name: "Name"
edit: "Bearbeiten"
export: "Exportieren"
import: "Importieren"
import_error_conflict: "Beim Importieren von %{name} wurde ein Konflikt erkannt. Möchtest du die vorhandene Persona aktualisieren?"
overwrite: "Überschreiben"
description: "Beschreibung"
no_llm_selected: "Kein Sprachmodell ausgewählt"
use_parent_llm: "Verwende das Personas Sprachmodell"
@@ -370,7 +430,10 @@ de:
title: "KI-Bot-Optionen"
save_first: "Weitere KI-Bot-Optionen werden verfügbar, sobald du die Persona gespeichert hast."
filters:
text: "Finde eine Persona"
reset: "Zurücksetzen"
no_results: "Es wurden keine Personas gefunden, die deinen Filtern entsprechen."
all_features: "Jede Funktion"
features_list:
one: "Funktion:"
other: "Funktionen:"
@@ -401,11 +464,14 @@ de:
back: "Zurück"
short_title: "Tools"
export: "Exportieren"
import: "Importieren"
import_error_conflict: "Tool existiert bereits, möchtest du es aktualisieren?"
overwrite: "Überschreiben"
no_tools: "Du hast noch keine Tools erstellt"
name: "Name"
name_help: "Der Name wird in der Discourse-Benutzeroberfläche angezeigt und ist die Kurzkennung, die Du verwendest, um das Werkzeug in verschiedenen Einstellungen zu finden. Er sollte eindeutig sein (er ist erforderlich)."
name_help: "Der Name wird in der Discourse-Benutzeroberfläche angezeigt und ist die Kurzkennung, die du verwendest, um das Tool in verschiedenen Einstellungen zu finden. Er sollte eindeutig sein (er ist erforderlich)."
new: "Neues Tool"
tool_name: "Werkzeugname"
tool_name: "Name des Tools"
tool_name_help: "Der Werkzeugname wird dem großen Sprachmodell präsentiert. Es ist nicht eindeutig, aber es ist von Person zu Person unterschiedlich. (Persona wird beim Speichern validiert)"
description: "Beschreibung"
description_help: "Eine klare Beschreibung des Zwecks des Tools für das Sprachmodell"
@@ -486,14 +552,18 @@ de:
ai_summarization: "Zusammenfassen"
ai_embeddings_semantic_search: "KI-Suche"
ai_spam: "Spam"
automation: "Automatisierung (%{name})"
automation: "Automatisierung (%{persona})"
in_use_warning:
one: "Dieses Modell wird derzeit von %{settings} verwendet. Wenn es falsch konfiguriert ist, wird die Funktion nicht wie erwartet funktionieren."
other: "Dieses Modell wird derzeit verwendet von: %{settings}. Wenn es falsch konfiguriert ist, werden die Funktionen nicht wie erwartet funktionieren. "
model_description:
none: "Allgemeine Einstellungen, die für die meisten Sprachmodelle funktionieren"
anthropic-claude-opus-4-0: "Das intelligenteste Modell von Anthropic"
anthropic-claude-sonnet-4-0: "Optimales Gleichgewicht zwischen Geschwindigkeit und Kosten"
anthropic-claude-3-7-sonnet-latest: "Optimales Gleichgewicht zwischen Geschwindigkeit und Kosten (vorherige Generation)"
anthropic-claude-3-5-haiku-latest: "Schnell und kosteneffizient"
google-gemini-2-5-pro: "Großes multimodales Modell, das eine Vielzahl von Aufgaben bewältigen kann"
google-gemini-2-0-flash: "Leicht, schnell und kosteneffizient mit multimodaler Argumentation (vorherige Generation)"
google-gemini-2-5-flash: "Leicht, schnell und kosteneffizient mit multimodaler Argumentation"
google-gemini-2-0-flash-lite: "Kosteneffizientes Modell mit niedriger Latenz"
open_ai-o3: "Das leistungsfähigste Argumentationsmodell von Open AI"
@@ -505,6 +575,9 @@ de:
samba_nova-Meta-Llama-3-3-70B-Instruct": "Leistungsstarkes Mehrzweckmodell"
mistral-mistral-large-latest: "Das leistungsstärkste Modell von Mistral"
mistral-pixtral-large-latest: "Das leistungsstärkste Modell von Mistral mit Sichtfunktion"
open_router-x-ai-grok-3-beta: "Das neueste Modell von xAI"
open_router-deepseek-deepseek-r1-0528-free: "DeepSeeks neuestes Argumentationsmodell"
open_router-meta-llama-3-3-70b-instruct: "Leistungsstarkes mehrsprachiges Modell"
preseeded_model_description: "Vorkonfiguriertes Open-Source-Modell unter Verwendung von %{model}"
configured:
title: "Konfigurierte LLMs"
@@ -565,6 +638,7 @@ de:
reasoning_tokens: "Anzahl der für die Argumentation verwendeten Token"
disable_temperature: "Temperatur deaktivieren (einige Denkmodelle unterstützen keine Temperatur)"
disable_top_p: "Top P deaktivieren (einige Denkmodelle unterstützen Top P nicht)"
enable_responses_api: "Aktiviere die Antwort-API (bei bestimmten OpenAI-Modellen erforderlich)"
related_topics:
title: "Verwandte Themen"
pill: "Verwandt"
@@ -583,6 +657,7 @@ de:
trigger: "KI fragen"
loading: "KI generiert"
cancel: "Abbrechen"
regen: "Erneut versuchen"
confirm: "Bestätigen"
discard: "Verwerfen"
changes: "Empfohlene Änderungen"
@@ -700,6 +775,7 @@ de:
shortcut_title: "Starte eine PN mit einem KI-Bot"
share: "KI-Unterhaltung kopieren"
conversation_shared: "Unterhaltung kopiert"
embed_copied: "Einbettung in die Zwischenablage kopiert"
debug_ai: "Rohdaten der KI-Anfrage und -Antwort anzeigen"
sidebar_empty: "Der Verlauf der Bot-Konversation wird hier angezeigt."
debug_ai_modal:
@@ -759,6 +835,7 @@ de:
today: "Heute"
last_7_days: "Letzte 7 Tage"
last_30_days: "Letzte 30 Tage"
upload_files: "Dateien hochladen"
sentiments:
dashboard:
title: "Stimmung"
+9 -1
View File
@@ -106,6 +106,9 @@ en:
top_p:
label: "Top P"
description: "Top P to use for the LLM, increase to increase randomness (leave empty to use model default)"
persona_id:
label: "Persona"
description: "AI Persona to use for report generation"
llm_tool_triage:
fields:
@@ -181,6 +184,10 @@ en:
discourse_ai:
title: "AI"
default_llm:
title: "Default LLM model"
description: "The default LLM model to use for all AI features. This will be used if no LLM is specified in the feature configuration or persona. If no default LLM is specified, the last created LLM will be used."
features:
short_title: "Features"
description: "These are the AI features available to visitors on your site. These can be configured to use specific personas and LLMs, and can be access controlled by groups."
@@ -563,7 +570,7 @@ en:
max_usages_required: "Must be set if max tokens is not set"
usage:
ai_bot: "AI bot"
ai_helper: "Helper"
ai_helper: "Helper (%{persona})"
ai_helper_image_caption: "Image caption"
ai_persona: "Persona (%{persona})"
ai_summarization: "Summarize"
@@ -872,6 +879,7 @@ en:
last_7_days: "Last 7 days"
last_30_days: "Last 30 days"
upload_files: "Upload files"
uploads_in_progress: "Cannot submit while uploads are in progress"
sentiments:
dashboard:
title: "Sentiment"
+2
View File
@@ -154,6 +154,8 @@ es:
reset: "Restablecer"
search:
name: "Buscar"
embeddings:
name: "Incrustaciones"
ai_helper:
name: "Ayudante"
proofread: Corregir el texto
+2
View File
@@ -154,6 +154,8 @@ fi:
reset: "Palauta"
search:
name: "Hae"
embeddings:
name: "Upotukset"
ai_helper:
name: "Apuri"
proofread: Oikolue teksti
+2
View File
@@ -154,6 +154,8 @@ fr:
reset: "Réinitialiser"
search:
name: "Recherche"
embeddings:
name: "Intégrations"
ai_helper:
name: "Assistant"
proofread: Relisez le texte
+22 -1
View File
@@ -185,6 +185,7 @@ he:
two: "LLMים:"
many: "LLMים:"
other: "LLMים:"
no_llm: "לא נבחר מש״ג/LLM"
no_persona: "לא מוגדר"
no_groups: "ללא"
edit: "עריכה"
@@ -196,24 +197,44 @@ he:
collapse_list: "(להציג פחות)"
bot:
name: "בוט"
description: "צ׳אט בוט שיכול לענות על שאלות ולסייע למשתמשים בהודעות אישיות, בפורום ובצ׳אט"
filters:
all: "הכול"
text: "חיפוש יכולות, דמויות, LLMים/מש״גים או קבוצות…"
reset: "איפוס"
summarization:
name: "סיכומים"
description: "להנגיש את כפתור הסיכום שמאפשר למבקרים לסכם נושאים"
topic_summaries: "תקצירי נושאים."
search:
name: "חיפוש"
description: "שיפור חוויית החיפוש על ידי אספקת תשובות שנוצרו על ידי בינה מלאכותית לשאילתות"
discoveries: "תגליות"
embeddings:
name: "הטמעות"
discord:
name: "שילוב מול Discord"
description: "הוספת היכולת לחפש בערוצי Discord"
search: "חיפוש ב־Discord"
inference:
generate_concepts: "הסקת תפיסות"
ai_helper:
name: "מסייע"
proofread: הגהת הטקסט
title_suggestions: "הצעת כותרות"
explain: "הסבר"
illustrate_post: "איור פוסט"
smart_dates: "תאריכים חכמים"
translate: "תרגום"
markdown_tables: "יצירת טבלה ב־Markdown"
custom_prompt: "בקשה מותאמת אישית"
image_caption: "הוספת כותרות לתמונות"
translation:
description: "תרגום תוכן לשפות נתמכות"
locale_detector: "מזהה שפה ומקום"
post_raw_translator: "מתרגם פוסטים גולמי"
topic_title_translator: "מתרגם כותרות נושאים"
short_text_translator: "מתרגם טקסטים קצרים"
spam:
name: "ספאם"
modals:
@@ -491,7 +512,7 @@ he:
ai_summarization: "סיכום"
ai_embeddings_semantic_search: "חיפוש בינה מלאכותית"
ai_spam: "ספאם"
automation: "אוטומציה (%{name})"
automation: "אוטומציה (%{persona})"
in_use_warning:
one: "המודל הזה משמש את %{settings}. אם לא מוגדר כראוי, היכולת לא תעבוד כמצופה."
two: "המודל הזה משמש את: %{settings}. אם ההגדרות שגויות היכולת לא תעבודנה כמצופה."

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