Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
67bc4c038a |
@@ -6,7 +6,8 @@ module Jobs
|
||||
sidekiq_options retry: false
|
||||
|
||||
def execute(args)
|
||||
return if !DiscourseAi::Translation.enabled?
|
||||
return if !SiteSetting.discourse_ai_enabled
|
||||
return if !SiteSetting.ai_translation_enabled
|
||||
return if args[:post_id].blank?
|
||||
|
||||
post = Post.find_by(id: args[:post_id])
|
||||
@@ -35,14 +36,12 @@ 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 DiscourseAi::Translation::LocaleNormalizer.is_same?(locale, detected_locale)
|
||||
regionless_locale = locale.split("_").first
|
||||
next if post.post_localizations.where("locale LIKE ?", "#{regionless_locale}%").exists?
|
||||
next if locale == detected_locale
|
||||
next if post.post_localizations.exists?(locale:)
|
||||
|
||||
begin
|
||||
DiscourseAi::Translation::PostLocalizer.localize(post, locale)
|
||||
|
||||
@@ -6,7 +6,8 @@ module Jobs
|
||||
sidekiq_options retry: false
|
||||
|
||||
def execute(args)
|
||||
return if !DiscourseAi::Translation.enabled?
|
||||
return if !SiteSetting.discourse_ai_enabled
|
||||
return if !SiteSetting.ai_translation_enabled
|
||||
return if args[:topic_id].blank?
|
||||
|
||||
topic = Topic.find_by(id: args[:topic_id])
|
||||
@@ -33,14 +34,12 @@ 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 DiscourseAi::Translation::LocaleNormalizer.is_same?(locale, detected_locale)
|
||||
regionless_locale = locale.split("_").first
|
||||
next if topic.topic_localizations.where("locale LIKE ?", "#{regionless_locale}%").exists?
|
||||
next if locale == detected_locale
|
||||
next if topic.topic_localizations.exists?(locale:)
|
||||
|
||||
begin
|
||||
DiscourseAi::Translation::TopicLocalizer.localize(topic, locale)
|
||||
|
||||
@@ -6,17 +6,21 @@ 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?
|
||||
|
||||
@@ -29,7 +33,6 @@ 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)
|
||||
|
||||
@@ -9,25 +9,23 @@ module Jobs
|
||||
limit = args[:limit]
|
||||
raise Discourse::InvalidParameters.new(:limit) if limit.blank? || limit <= 0
|
||||
|
||||
return if !DiscourseAi::Translation.backfill_enabled?
|
||||
return if !SiteSetting.discourse_ai_enabled
|
||||
return if !SiteSetting.ai_translation_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 LIKE '#{base_locale}%'",
|
||||
)
|
||||
.where(
|
||||
"posts.created_at > ?",
|
||||
SiteSetting.ai_translation_backfill_max_age_days.days.ago,
|
||||
"LEFT JOIN post_localizations pl ON pl.post_id = posts.id AND pl.locale = #{ActiveRecord::Base.connection.quote(locale)}",
|
||||
)
|
||||
.where(deleted_at: nil)
|
||||
.where("posts.user_id > 0")
|
||||
.where.not(raw: [nil, ""])
|
||||
.where.not(locale: nil)
|
||||
.where("posts.locale NOT LIKE '#{base_locale}%'")
|
||||
.where.not(locale: locale)
|
||||
.where("pl.id IS NULL")
|
||||
|
||||
posts = posts.joins(:topic)
|
||||
@@ -48,6 +46,14 @@ 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?
|
||||
|
||||
@@ -9,24 +9,22 @@ module Jobs
|
||||
limit = args[:limit]
|
||||
raise Discourse::InvalidParameters.new(:limit) if limit.blank? || limit <= 0
|
||||
|
||||
return if !DiscourseAi::Translation.backfill_enabled?
|
||||
return if !SiteSetting.discourse_ai_enabled
|
||||
return if !SiteSetting.ai_translation_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 LIKE '#{base_locale}%'",
|
||||
)
|
||||
.where(
|
||||
"topics.created_at > ?",
|
||||
SiteSetting.ai_translation_backfill_max_age_days.days.ago,
|
||||
"LEFT JOIN topic_localizations tl ON tl.topic_id = topics.id AND tl.locale = #{ActiveRecord::Base.connection.quote(locale)}",
|
||||
)
|
||||
.where(deleted_at: nil)
|
||||
.where("topics.user_id > 0")
|
||||
.where.not(locale: nil)
|
||||
.where("topics.locale NOT LIKE '#{base_locale}%'")
|
||||
.where.not(locale: locale)
|
||||
.where("tl.id IS NULL")
|
||||
|
||||
if SiteSetting.ai_translation_backfill_limit_to_public_content
|
||||
@@ -45,6 +43,14 @@ 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,7 +7,10 @@ module Jobs
|
||||
cluster_concurrency 1
|
||||
|
||||
def execute(args)
|
||||
return if !DiscourseAi::Translation.backfill_enabled?
|
||||
return if !SiteSetting.discourse_ai_enabled
|
||||
return if !SiteSetting.ai_translation_enabled
|
||||
limit = SiteSetting.ai_translation_backfill_hourly_rate
|
||||
return if limit == 0
|
||||
|
||||
categories = Category.where(locale: nil)
|
||||
|
||||
@@ -15,7 +18,6 @@ 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,8 +6,11 @@ module Jobs
|
||||
cluster_concurrency 1
|
||||
|
||||
def execute(args)
|
||||
return if !DiscourseAi::Translation.backfill_enabled?
|
||||
return if !SiteSetting.discourse_ai_enabled
|
||||
return if !SiteSetting.ai_translation_enabled
|
||||
return if SiteSetting.content_localization_supported_locales.blank?
|
||||
limit = SiteSetting.ai_translation_backfill_hourly_rate
|
||||
return if limit == 0
|
||||
|
||||
Jobs.enqueue(:localize_categories, limit:)
|
||||
end
|
||||
|
||||
@@ -6,8 +6,10 @@ module Jobs
|
||||
cluster_concurrency 1
|
||||
|
||||
def execute(args)
|
||||
return if !DiscourseAi::Translation.backfill_enabled?
|
||||
return if !SiteSetting.discourse_ai_enabled
|
||||
return if !SiteSetting.ai_translation_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 !DiscourseAi::Translation.backfill_enabled?
|
||||
|
||||
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
|
||||
|
||||
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,6 +33,14 @@ 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?
|
||||
|
||||
|
||||
@@ -36,12 +36,7 @@ module ::Jobs
|
||||
existing_summary = strategy.existing_summary
|
||||
|
||||
if existing_summary.blank? || existing_summary.outdated
|
||||
begin
|
||||
strategy.summarize(user)
|
||||
rescue => e
|
||||
Rails.logger.error("Error summarizing topic #{topic.id}: #{e.class.name} - #{e.message}")
|
||||
raise e
|
||||
end
|
||||
strategy.summarize(user)
|
||||
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,9 +6,13 @@ module Jobs
|
||||
cluster_concurrency 1
|
||||
|
||||
def execute(args)
|
||||
return if !DiscourseAi::Translation.backfill_enabled?
|
||||
return if !SiteSetting.discourse_ai_enabled
|
||||
return if !SiteSetting.ai_translation_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,15 +7,12 @@ module Jobs
|
||||
cluster_concurrency 1
|
||||
|
||||
def execute(args)
|
||||
return if !DiscourseAi::Translation.backfill_enabled?
|
||||
|
||||
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
|
||||
|
||||
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)
|
||||
topics = Topic.where(locale: nil, deleted_at: nil).where("topics.user_id > 0")
|
||||
|
||||
if SiteSetting.ai_translation_backfill_limit_to_public_content
|
||||
topics =
|
||||
@@ -31,6 +28,14 @@ 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,6 +12,7 @@ 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
|
||||
@@ -364,6 +365,17 @@ 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?
|
||||
|
||||
|
||||
@@ -84,7 +84,7 @@ class EmbeddingDefinition < ActiveRecord::Base
|
||||
dimensions: 2000,
|
||||
max_sequence_length: 8191,
|
||||
pg_function: "<=>",
|
||||
tokenizer_class: "DiscourseAi::Tokenizer::OpenAiCl100kTokenizer",
|
||||
tokenizer_class: "DiscourseAi::Tokenizer::OpenAiTokenizer",
|
||||
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::OpenAiCl100kTokenizer",
|
||||
tokenizer_class: "DiscourseAi::Tokenizer::OpenAiTokenizer",
|
||||
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::OpenAiCl100kTokenizer",
|
||||
tokenizer_class: "DiscourseAi::Tokenizer::OpenAiTokenizer",
|
||||
url: "https://api.openai.com/v1/embeddings",
|
||||
provider: OPEN_AI,
|
||||
provider_params: {
|
||||
|
||||
@@ -99,7 +99,7 @@ class LlmModel < ActiveRecord::Base
|
||||
end
|
||||
|
||||
def identifier
|
||||
"#{id}"
|
||||
"custom:#{id}"
|
||||
end
|
||||
|
||||
def toggle_companion_user
|
||||
|
||||
@@ -260,11 +260,10 @@ 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({
|
||||
uploads: this.uploads,
|
||||
inProgressUploadsCount: this.inProgressUploads.length,
|
||||
});
|
||||
await this.aiBotConversationsHiddenSubmit.submitToBot();
|
||||
this.uploads = new TrackedArray();
|
||||
} catch (error) {
|
||||
popupAjaxError(error);
|
||||
|
||||
@@ -1,46 +0,0 @@
|
||||
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,9 +66,12 @@ class ExpandableList extends Component {
|
||||
|
||||
export default class AiFeaturesList extends Component {
|
||||
get sortedModules() {
|
||||
const modules = this.args.modules?.toArray() || this.args.modules;
|
||||
return [...(modules || [])].toSorted((a, b) =>
|
||||
(a.module_name || "").localeCompare(b.module_name || "")
|
||||
if (!this.args.modules || !this.args.modules.length) {
|
||||
return [];
|
||||
}
|
||||
|
||||
return this.args.modules.toSorted((a, b) =>
|
||||
a.module_name.localeCompare(b.module_name)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -9,7 +9,6 @@ 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";
|
||||
@@ -203,8 +202,6 @@ export default class AiFeatures extends Component {
|
||||
/>
|
||||
</div>
|
||||
|
||||
<AiDefaultLlmSelector />
|
||||
|
||||
{{#if this.filteredFeatures.length}}
|
||||
<AiFeaturesList @modules={{this.filteredFeatures}} />
|
||||
{{else}}
|
||||
|
||||
@@ -9,7 +9,6 @@ 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) {
|
||||
@@ -138,9 +137,6 @@ 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 = model.llm_id;
|
||||
this.selectedLLM = "custom:" + 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;
|
||||
return this.selectedLLM.toString().split(":")[1];
|
||||
}
|
||||
|
||||
@action
|
||||
|
||||
@@ -18,6 +18,7 @@ export default class AiBotConversationsHiddenSubmit extends Service {
|
||||
|
||||
personaId;
|
||||
targetUsername;
|
||||
uploads = [];
|
||||
|
||||
inputValue = "";
|
||||
|
||||
@@ -31,7 +32,7 @@ export default class AiBotConversationsHiddenSubmit extends Service {
|
||||
}
|
||||
|
||||
@action
|
||||
async submitToBot(uploadData) {
|
||||
async submitToBot() {
|
||||
if (
|
||||
this.inputValue.length <
|
||||
this.siteSettings.min_personal_message_post_length
|
||||
@@ -47,7 +48,7 @@ export default class AiBotConversationsHiddenSubmit extends Service {
|
||||
}
|
||||
|
||||
// Don't submit if there are still uploads in progress
|
||||
if (uploadData.inProgressUploadsCount > 0) {
|
||||
if (document.querySelector(".ai-bot-upload--in-progress")) {
|
||||
return this.dialog.alert({
|
||||
message: i18n("discourse_ai.ai_bot.conversations.uploads_in_progress"),
|
||||
});
|
||||
@@ -60,10 +61,10 @@ export default class AiBotConversationsHiddenSubmit extends Service {
|
||||
let rawContent = this.inputValue;
|
||||
|
||||
// Append upload markdown if we have uploads
|
||||
if (uploadData.uploads && uploadData.uploads.length > 0) {
|
||||
if (this.uploads && this.uploads.length > 0) {
|
||||
rawContent += "\n\n";
|
||||
|
||||
uploadData.uploads.forEach((upload) => {
|
||||
this.uploads.forEach((upload) => {
|
||||
const uploadMarkdown = getUploadMarkdown(upload);
|
||||
rawContent += uploadMarkdown + "\n";
|
||||
});
|
||||
@@ -82,6 +83,7 @@ export default class AiBotConversationsHiddenSubmit extends Service {
|
||||
});
|
||||
|
||||
// Reset uploads after successful submission
|
||||
this.uploads = [];
|
||||
this.inputValue = "";
|
||||
|
||||
this.appEvents.trigger("discourse-ai:bot-pm-created", {
|
||||
|
||||
@@ -158,51 +158,3 @@
|
||||
.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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -158,8 +158,6 @@ ar:
|
||||
reset: "إعادة التعيين"
|
||||
search:
|
||||
name: "البحث"
|
||||
embeddings:
|
||||
name: "التضمينات"
|
||||
ai_helper:
|
||||
name: "المساعد"
|
||||
proofread: تدقيق النص لغويًا
|
||||
|
||||
@@ -29,22 +29,6 @@ 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:
|
||||
@@ -114,21 +98,21 @@ de:
|
||||
fields:
|
||||
model:
|
||||
label: "Modell"
|
||||
description: "Das für die Sichtung verwendete Standard-Sprachmodell"
|
||||
description: "Das für die Triage verwendete Standard-Sprachmodell"
|
||||
tool:
|
||||
label: "Tool"
|
||||
description: "Tool für die Sichtung (das Tool darf keine Parameter definiert haben)"
|
||||
label: "Werkzeug"
|
||||
description: "Werkzeug für die Triage (das Werkzeug darf keine Parameter definiert haben)"
|
||||
llm_persona_triage:
|
||||
fields:
|
||||
persona:
|
||||
label: "Persona"
|
||||
description: "KI-Persona, die für die Sichtung verwendet werden soll (Standard-LLM und -Benutzer müssen eingestellt sein)"
|
||||
description: "KI-Persona, die für die Triage 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 Sichtung mit Tools"
|
||||
description: "Im stillen Modus empfängt die Persona den Inhalt, schreibt aber nichts in das Forum - nützlich bei der Triage mit Tools"
|
||||
llm_triage:
|
||||
fields:
|
||||
system_prompt:
|
||||
@@ -193,7 +177,6 @@ de:
|
||||
llm:
|
||||
one: "LLM:"
|
||||
other: "LLM:"
|
||||
no_llm: "Kein LLM ausgewählt"
|
||||
no_persona: "Nicht festgelegt"
|
||||
no_groups: "Keiner"
|
||||
edit: "Bearbeiten"
|
||||
@@ -201,75 +184,35 @@ 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"
|
||||
@@ -350,9 +293,6 @@ 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"
|
||||
@@ -430,10 +370,7 @@ 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:"
|
||||
@@ -464,14 +401,11 @@ 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 Tool 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 Werkzeug in verschiedenen Einstellungen zu finden. Er sollte eindeutig sein (er ist erforderlich)."
|
||||
new: "Neues Tool"
|
||||
tool_name: "Name des Tools"
|
||||
tool_name: "Werkzeugname"
|
||||
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"
|
||||
@@ -552,18 +486,14 @@ de:
|
||||
ai_summarization: "Zusammenfassen"
|
||||
ai_embeddings_semantic_search: "KI-Suche"
|
||||
ai_spam: "Spam"
|
||||
automation: "Automatisierung (%{persona})"
|
||||
automation: "Automatisierung (%{name})"
|
||||
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"
|
||||
@@ -575,9 +505,6 @@ 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"
|
||||
@@ -638,7 +565,6 @@ 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"
|
||||
@@ -657,7 +583,6 @@ de:
|
||||
trigger: "KI fragen"
|
||||
loading: "KI generiert"
|
||||
cancel: "Abbrechen"
|
||||
regen: "Erneut versuchen"
|
||||
confirm: "Bestätigen"
|
||||
discard: "Verwerfen"
|
||||
changes: "Empfohlene Änderungen"
|
||||
@@ -775,7 +700,6 @@ 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:
|
||||
@@ -835,7 +759,6 @@ de:
|
||||
today: "Heute"
|
||||
last_7_days: "Letzte 7 Tage"
|
||||
last_30_days: "Letzte 30 Tage"
|
||||
upload_files: "Dateien hochladen"
|
||||
sentiments:
|
||||
dashboard:
|
||||
title: "Stimmung"
|
||||
|
||||
@@ -106,9 +106,6 @@ 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:
|
||||
@@ -184,10 +181,6 @@ 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."
|
||||
@@ -570,7 +563,7 @@ en:
|
||||
max_usages_required: "Must be set if max tokens is not set"
|
||||
usage:
|
||||
ai_bot: "AI bot"
|
||||
ai_helper: "Helper (%{persona})"
|
||||
ai_helper: "Helper"
|
||||
ai_helper_image_caption: "Image caption"
|
||||
ai_persona: "Persona (%{persona})"
|
||||
ai_summarization: "Summarize"
|
||||
@@ -879,7 +872,6 @@ 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"
|
||||
|
||||
@@ -154,8 +154,6 @@ es:
|
||||
reset: "Restablecer"
|
||||
search:
|
||||
name: "Buscar"
|
||||
embeddings:
|
||||
name: "Incrustaciones"
|
||||
ai_helper:
|
||||
name: "Ayudante"
|
||||
proofread: Corregir el texto
|
||||
|
||||
@@ -154,8 +154,6 @@ fi:
|
||||
reset: "Palauta"
|
||||
search:
|
||||
name: "Hae"
|
||||
embeddings:
|
||||
name: "Upotukset"
|
||||
ai_helper:
|
||||
name: "Apuri"
|
||||
proofread: Oikolue teksti
|
||||
|
||||
@@ -154,8 +154,6 @@ fr:
|
||||
reset: "Réinitialiser"
|
||||
search:
|
||||
name: "Recherche"
|
||||
embeddings:
|
||||
name: "Intégrations"
|
||||
ai_helper:
|
||||
name: "Assistant"
|
||||
proofread: Relisez le texte
|
||||
|
||||
@@ -185,7 +185,6 @@ he:
|
||||
two: "LLMים:"
|
||||
many: "LLMים:"
|
||||
other: "LLMים:"
|
||||
no_llm: "לא נבחר מש״ג/LLM"
|
||||
no_persona: "לא מוגדר"
|
||||
no_groups: "ללא"
|
||||
edit: "עריכה"
|
||||
@@ -197,44 +196,24 @@ 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:
|
||||
@@ -512,7 +491,7 @@ he:
|
||||
ai_summarization: "סיכום"
|
||||
ai_embeddings_semantic_search: "חיפוש בינה מלאכותית"
|
||||
ai_spam: "ספאם"
|
||||
automation: "אוטומציה (%{persona})"
|
||||
automation: "אוטומציה (%{name})"
|
||||
in_use_warning:
|
||||
one: "המודל הזה משמש את %{settings}. אם לא מוגדר כראוי, היכולת לא תעבוד כמצופה."
|
||||
two: "המודל הזה משמש את: %{settings}. אם ההגדרות שגויות היכולת לא תעבודנה כמצופה."
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user