Compare commits

..
Author SHA1 Message Date
Sam Saffron 67bc4c038a FEATURE: bring back hidden settings
The hidden settings have general use and may be already set.

Helper one is particularly important cause it sets a default for a large
number of settings

Additionally hiding access means people can not remove LLMs without console
access
2025-07-09 16:06:27 +10:00
173 changed files with 787 additions and 1448 deletions
+4 -5
View File
@@ -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)
+4 -5
View File
@@ -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 -3
View File
@@ -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)
+14 -8
View File
@@ -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?
+14 -8
View File
@@ -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?
+1 -6
View File
@@ -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
View File
@@ -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?
+3 -3
View File
@@ -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: {
+1 -1
View File
@@ -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;
}
}
-2
View File
@@ -158,8 +158,6 @@ ar:
reset: "إعادة التعيين"
search:
name: "البحث"
embeddings:
name: "التضمينات"
ai_helper:
name: "المساعد"
proofread: تدقيق النص لغويًا
+8 -85
View File
@@ -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"
+1 -9
View File
@@ -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"
-2
View File
@@ -154,8 +154,6 @@ es:
reset: "Restablecer"
search:
name: "Buscar"
embeddings:
name: "Incrustaciones"
ai_helper:
name: "Ayudante"
proofread: Corregir el texto
-2
View File
@@ -154,8 +154,6 @@ fi:
reset: "Palauta"
search:
name: "Hae"
embeddings:
name: "Upotukset"
ai_helper:
name: "Apuri"
proofread: Oikolue teksti
-2
View File
@@ -154,8 +154,6 @@ fr:
reset: "Réinitialiser"
search:
name: "Recherche"
embeddings:
name: "Intégrations"
ai_helper:
name: "Assistant"
proofread: Relisez le texte
+1 -22
View File
@@ -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