Compare commits

..
Author SHA1 Message Date
Jarek Radosz da55eb31d0 DEV: Deprecate the plugin 2025-07-14 18:14:14 +02:00
135 changed files with 581 additions and 879 deletions
+3
View File
@@ -1,5 +1,8 @@
# **Discourse AI** Plugin
> [!IMPORTANT]
> This plugin has now been bundled into Discourse core. See: https://meta.discourse.org/t/bundling-more-popular-plugins-with-discourse-core/373574
**Plugin Summary**
For more information, please see: https://meta.discourse.org/t/discourse-ai/259214?u=falco
+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
@@ -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
@@ -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;
}
}
-3
View File
@@ -233,9 +233,7 @@ de:
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"
@@ -775,7 +773,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:
+1 -5
View File
@@ -184,10 +184,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 +566,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"
+1 -3
View File
@@ -116,7 +116,7 @@ de:
ai_translation_model: "Das Modell, das für die Übersetzung verwendet werden soll. Dieses Modell muss die Übersetzung unterstützen. Personas können diese Einstellung überschreiben."
ai_translation_backfill_limit_to_public_content: "Wenn diese Option aktiviert ist, werden nur Inhalte in öffentlichen Kategorien übersetzt. Wenn sie deaktiviert ist, werden auch Inhalte in Gruppen-PMs und privaten Kategorien zur Übersetzung gesendet."
ai_translation_max_post_length: "Die maximale Länge eines zu übersetzenden Beitrags. Längere Beiträge werden nicht übersetzt."
ai_translation_backfill_max_age_days: "Das maximale Alter eines Beitrags oder Themas, das übersetzt werden soll. Beiträge und Themen, die älter sind, werden nicht übersetzt. 0 deaktiviert die rückwirkende Übersetzung, aber nicht die Übersetzung von neuen Beiträgen."
ai_translation_backfill_max_age_days: "Das maximale Alter eines zu übersetzenden Beitrags und Themas. Ältere Beiträge und Themen werden nicht übersetzt."
reviewables:
reasons:
flagged_by_toxicity: Das KI-Plug-in meldete dies nach der Klassifizierung als toxisch.
@@ -221,7 +221,6 @@ de:
one: "Du kannst nur %{count} Schlüssel im Artefakt haben."
other: "Du kannst nur %{count} Schlüssel im Artefakt haben."
link: "In neuem Tab anzeigen"
copy_embed: "Einbettung kopieren"
view_source: "Quelle anzeigen"
view_changes: "Änderungen anzeigen"
change_description: "Beschreibung ändern"
@@ -591,7 +590,6 @@ de:
bedrock_invalid_url: "Bitte fülle alle Felder aus, um dieses Modell zu verwenden."
ai_staff_action_logger:
updated: "aktualisiert"
set: "gesetzt"
removed: "entfernt"
errors:
quota_exceeded: "Du hast das Kontingent für dieses Modell überschritten. Bitte versuche es erneut in %{relative_time}."
+1 -2
View File
@@ -25,7 +25,6 @@ en:
description: "Periodic report based on a large language model"
site_settings:
discourse_ai_enabled: "Enable the discourse AI plugin."
ai_default_llm_model: "The default LLM model to use for all AI features"
ai_artifact_security: "The AI artifact system generates IFRAMEs with runnable code. Strict mode forces an extra click to run code. Lax mode runs code immediately. Hybrid mode allows user to supply data-ai-artifact-autorun to show right away. Disabled mode disables the artifact system."
ai_toxicity_enabled: "Enable the toxicity module."
ai_toxicity_inference_service_api_endpoint: "URL where the API is running for the toxicity module"
@@ -583,7 +582,7 @@ en:
llm:
configuration:
create_llm: "You need to setup an LLM before enabling this feature"
disable_modules_first: "You must disable these modules first: %{settings}"
disable_module_first: "You have to disable %{setting} first."
set_llm_first: "Set %{setting} first"
model_unreachable: "We couldn't get a response from this model. Check your settings first."
invalid_seeded_model: "You can't use this model with this feature"
+8 -12
View File
@@ -11,12 +11,6 @@ discourse_ai:
- "lax"
- "hybrid"
- "strict"
ai_default_llm_model:
default: ""
type: enum
allow_any: false
enum: "DiscourseAi::Configuration::LlmEnumerator"
validator: "DiscourseAi::Configuration::LlmValidator"
ai_sentiment_enabled:
default: false
@@ -109,7 +103,7 @@ discourse_ai:
default: false
client: true
area: "ai-features/ai_helper"
ai_helper_model: # Deprecated. TODO(keegan): Remove 2025-09-01
ai_helper_model:
default: ""
allow_any: false
type: enum
@@ -155,7 +149,7 @@ discourse_ai:
- "context_menu"
- "image_caption"
area: "ai-features/ai_helper"
ai_helper_image_caption_model: # Deprecated. TODO(keegan): Remove 2025-09-01
ai_helper_image_caption_model:
default: ""
type: enum
enum: "DiscourseAi::Configuration::LlmVisionEnumerator"
@@ -266,12 +260,12 @@ discourse_ai:
client: true
validator: "DiscourseAi::Configuration::LlmDependencyValidator"
area: "ai-features/embeddings"
ai_embeddings_semantic_search_hyde_model: # Deprecated. TODO(keegan): Remove 2025-09-01
hidden: true
ai_embeddings_semantic_search_hyde_model:
default: ""
type: enum
allow_any: false
enum: "DiscourseAi::Configuration::LlmEnumerator"
validator: "DiscourseAi::Configuration::LlmValidator"
area: "ai-features/embeddings"
ai_embeddings_semantic_search_hyde_model_allowed_seeded_models:
default: ""
@@ -322,11 +316,12 @@ discourse_ai:
client: true
validator: "DiscourseAi::Configuration::LlmDependencyValidator"
area: "ai-features/summarization"
ai_summarization_model: # Deprecated. TODO(keegan): Remove 2025-09-01
ai_summarization_model:
default: ""
allow_any: false
type: enum
enum: "DiscourseAi::Configuration::LlmEnumerator"
validator: "DiscourseAi::Configuration::LlmValidator"
hidden: true
ai_summarization_persona:
default: "-11"
@@ -488,11 +483,12 @@ discourse_ai:
client: true
validator: "DiscourseAi::Configuration::LlmDependencyValidator"
area: "ai-features/translation"
ai_translation_model: # Deprecated. TODO(keegan): Remove 2025-09-01
ai_translation_model:
default: ""
type: enum
allow_any: false
enum: "DiscourseAi::Configuration::LlmEnumerator"
validator: "DiscourseAi::Configuration::LlmValidator"
area: "ai-features/translation"
ai_translation_locale_detector_persona:
default: "-27"
@@ -1,16 +0,0 @@
# frozen_string_literal: true
class SeedAiDefaultLlmModel < ActiveRecord::Migration[7.2]
def up
return if DB.query_single("SELECT 1 FROM llm_models LIMIT 1").empty?
last_model_id = DB.query_single("SELECT id FROM llm_models ORDER BY id DESC LIMIT 1").first
if last_model_id.present?
execute "UPDATE site_settings SET value = '#{last_model_id}' WHERE name = 'ai_default_llm_model' AND (value IS NULL OR value = '');"
end
end
def down
raise ActiveRecord::IrreversibleMigration
end
end
@@ -1,33 +0,0 @@
# frozen_string_literal: true
class CopyAiSummarizationModelToPersonaDefault < ActiveRecord::Migration[7.2]
def up
ai_summarization_model =
DB.query_single("SELECT value FROM site_settings WHERE name = 'ai_summarization_model'").first
if ai_summarization_model.present? && ai_summarization_model.start_with?("custom:")
# Extract the model ID from the setting value (e.g., "custom:-5" -> "-5")
model_id = ai_summarization_model.split(":").last
persona_settings = %w[ai_summarization_persona ai_summary_gists_persona]
default_persona_ids = [-11, -12]
persona_ids_query =
persona_settings
.map { |setting| "SELECT value FROM site_settings WHERE name = '#{setting}'" }
.join(" UNION ")
persona_ids = DB.query_single(persona_ids_query).compact
all_persona_ids = (default_persona_ids + persona_ids).map(&:to_i).uniq.join(",")
# Update the summarization personas with the extracted model ID
execute(<<~SQL)
UPDATE ai_personas
SET default_llm_id = #{model_id}
WHERE id IN (#{all_persona_ids}) AND default_llm_id IS NULL
SQL
end
end
def down
raise ActiveRecord::IrreversibleMigration
end
end
@@ -1,43 +0,0 @@
# frozen_string_literal: true
class CopyAiHelperModelToPersonaDefault < ActiveRecord::Migration[7.2]
def up
ai_helper_model =
DB.query_single("SELECT value FROM site_settings WHERE name = 'ai_helper_model'").first
if ai_helper_model.present? && ai_helper_model.start_with?("custom:")
# Extract the model ID from the setting value (e.g., "custom:1" -> "1")
model_id = ai_helper_model.split(":").last
# Get persona IDs from site settings
persona_settings = %w[
ai_helper_proofreader_persona
ai_helper_title_suggestions_persona
ai_helper_explain_persona
ai_helper_smart_dates_persona
ai_helper_translator_persona
ai_helper_markdown_tables_persona
ai_helper_custom_prompt_persona
]
default_persona_ids = [-18, -19, -20, -22, -23, -24, -25]
persona_ids_query =
persona_settings
.map { |setting| "SELECT value FROM site_settings WHERE name = '#{setting}'" }
.join(" UNION ")
persona_ids = DB.query_single(persona_ids_query).compact
all_persona_ids = (default_persona_ids + persona_ids).map(&:to_i).uniq.join(",")
# Update the helper personas with the extracted model ID
execute(<<~SQL)
UPDATE ai_personas
SET default_llm_id = #{model_id}
WHERE id IN (#{all_persona_ids}) AND default_llm_id IS NULL
SQL
end
end
def down
raise ActiveRecord::IrreversibleMigration
end
end
@@ -1,35 +0,0 @@
# frozen_string_literal: true
class CopyHydeModelToPersona < ActiveRecord::Migration[7.2]
def up
hyde_model =
DB.query_single(
"SELECT value FROM site_settings WHERE name = 'ai_embeddings_semantic_search_hyde_model'",
).first
if hyde_model.present? && hyde_model.start_with?("custom:")
# Extract the model ID from the setting value (e.g., "custom:1" -> "1")
model_id = hyde_model.split(":").last
persona_settings = %w[ai_embeddings_semantic_search_hyde_persona]
default_persona_ids = [-32]
persona_ids_query =
persona_settings
.map { |setting| "SELECT value FROM site_settings WHERE name = '#{setting}'" }
.join(" UNION ")
persona_ids = DB.query_single(persona_ids_query).compact
all_persona_ids = (default_persona_ids + persona_ids).map(&:to_i).uniq.join(",")
# Update the hyde persona with the extracted model ID
execute(<<~SQL)
UPDATE ai_personas
SET default_llm_id = #{model_id}
WHERE id IN (#{all_persona_ids}) AND default_llm_id IS NULL
SQL
end
end
def down
raise ActiveRecord::IrreversibleMigration
end
end
@@ -1,14 +0,0 @@
# frozen_string_literal: true
class UpdateOpenAiEmbeddingsTokenizer < ActiveRecord::Migration[7.2]
def up
execute <<~SQL
UPDATE embedding_definitions
SET tokenizer_class = 'DiscourseAi::Tokenizer::OpenAiCl100kTokenizer'
WHERE url LIKE '%https://api.openai.com/%' AND tokenizer_class <> 'DiscourseAi::Tokenizer::OpenAiCl100kTokenizer'
SQL
end
def down
raise ActiveRecord::IrreversibleMigration
end
end
@@ -1,39 +0,0 @@
# frozen_string_literal: true
class CopyTranslationModelToPersona < ActiveRecord::Migration[7.2]
def up
ai_translation_model =
DB.query_single("SELECT value FROM site_settings WHERE name = 'ai_translation_model'").first
if ai_translation_model.present? && ai_translation_model.start_with?("custom:")
# Extract the model ID from the setting value (e.g., "custom:-5" -> "-5")
model_id = ai_translation_model.split(":").last
persona_settings = %w[
ai_translation_locale_detector_persona
ai_translation_post_raw_translator_persona
ai_translation_topic_title_translator_persona
ai_translation_short_text_translator_persona
]
default_persona_ids = [-27, -28, -29, -30]
persona_ids_query =
persona_settings
.map { |setting| "SELECT value FROM site_settings WHERE name = '#{setting}'" }
.join(" UNION ")
persona_ids = DB.query_single(persona_ids_query).compact
all_persona_ids = (default_persona_ids + persona_ids).map(&:to_i).uniq.join(",")
# Update the translation personas (IDs -27, -28, -29, -30) with the extracted model ID
execute(<<~SQL)
UPDATE ai_personas
SET default_llm_id = #{model_id}
WHERE id IN (#{all_persona_ids}) AND default_llm_id IS NULL
SQL
end
end
def down
raise ActiveRecord::IrreversibleMigration
end
end
@@ -1,37 +0,0 @@
# frozen_string_literal: true
class CopyAiImageCaptionModelToPersonaDefault < ActiveRecord::Migration[7.2]
def up
ai_helper_image_caption_model =
DB.query_single(
"SELECT value FROM site_settings WHERE name = 'ai_helper_image_caption_model'",
).first
if ai_helper_image_caption_model.present? &&
ai_helper_image_caption_model.start_with?("custom:")
# Extract the model ID from the setting value (e.g., "custom:1" -> "1")
model_id = ai_helper_image_caption_model.split(":").last
persona_settings = %w[ai_helper_post_illustrator_persona ai_helper_image_caption_persona]
default_persona_ids = [-21, -26]
persona_ids_query =
persona_settings
.map { |setting| "SELECT value FROM site_settings WHERE name = '#{setting}'" }
.join(" UNION ")
persona_ids = DB.query_single(persona_ids_query).compact
all_persona_ids = (default_persona_ids + persona_ids).map(&:to_i).uniq.join(",")
# Update the helper personas with the extracted model ID
execute(<<~SQL)
UPDATE ai_personas
SET default_llm_id = #{model_id}
WHERE id IN (#{all_persona_ids}) AND default_llm_id IS NULL
SQL
end
end
def down
raise ActiveRecord::IrreversibleMigration
end
end
+11 -2
View File
@@ -312,9 +312,18 @@ module DiscourseAi
# Priorities are:
# 1. Persona's default LLM
# 2. SiteSetting.ai_default_llm_id (or newest LLM if not set)
# 2. Hidden `ai_helper_model` setting, or `ai_helper_image_caption_model` for image_caption.
# 3. Newest LLM config
def self.find_ai_helper_model(helper_mode, persona_klass)
model_id = persona_klass.default_llm_id || SiteSetting.ai_default_llm_model
model_id = persona_klass.default_llm_id
if !model_id
if helper_mode == IMAGE_CAPTION
model_id = SiteSetting.ai_helper_image_caption_model&.split(":")&.last
else
model_id = SiteSetting.ai_helper_model&.split(":")&.last
end
end
if model_id.present?
LlmModel.find_by(id: model_id)
+1 -5
View File
@@ -12,14 +12,10 @@ module DiscourseAi
return nil if content.blank?
suggested_title = call_llm(content)
return nil if suggested_title.blank?
cleanup(suggested_title)
end
def call_llm(thread_content)
return nil if SiteSetting.ai_default_llm_model.blank?
chat = "<input>\n#{thread_content}\n</input>"
prompt =
@@ -34,7 +30,7 @@ module DiscourseAi
messages: [{ type: :user, content: chat, id: "User" }],
)
DiscourseAi::Completions::Llm.proxy(SiteSetting.ai_default_llm_model).generate(
DiscourseAi::Completions::Llm.proxy(SiteSetting.ai_helper_model).generate(
prompt,
user: Discourse.system_user,
stop_sequences: ["</input>"],
+1 -7
View File
@@ -57,12 +57,6 @@ module DiscourseAi
end
def diffusion_prompt(text, user)
llm_model =
AiPersona.find_by(id: SiteSetting.ai_helper_post_illustrator_persona)&.default_llm_id ||
SiteSetting.ai_default_llm_model
return nil if llm_model.blank?
prompt =
DiscourseAi::Completions::Prompt.new(
<<~TEXT.strip,
@@ -72,7 +66,7 @@ module DiscourseAi
messages: [{ type: :user, content: text, id: user.username }],
)
DiscourseAi::Completions::Llm.proxy(llm_model).generate(
DiscourseAi::Completions::Llm.proxy(SiteSetting.ai_helper_model).generate(
prompt,
user: user,
feature_name: "illustrate_post",
+1 -1
View File
@@ -37,7 +37,7 @@ module DiscourseAi
value_h["id"] > 0 ||
SiteSetting.ai_automation_allowed_seeded_models_map.include?(value_h["id"].to_s)
end
.each { |value_h| value_h["id"] = "#{value_h["id"]}" }
.each { |value_h| value_h["id"] = "custom:#{value_h["id"]}" }
values
end
+3 -1
View File
@@ -74,7 +74,7 @@ module DiscourseAi
else
I18n.t("discourse_automation.scriptables.llm_report.title")
end
@model = LlmModel.find_by(id: model)
@model = LlmModel.find_by(id: model.split(":")&.last)
@persona = AiPersona.find(persona_id).class_instance.new
@category_ids = category_ids
@tags = tags
@@ -154,10 +154,12 @@ module DiscourseAi
result = +""
bot = DiscourseAi::Personas::Bot.as(Discourse.system_user, persona: @persona, model: @model)
json_summary_schema_key = @persona.response_format&.first.to_h
output = nil
buffer_blk =
Proc.new do |partial, _, type|
if type == :structured_output
output = partial.dup
read_chunk = partial.read_buffered_property(json_summary_schema_key["key"]&.to_sym)
print read_chunk if Rails.env.development? && @debug_mode
-2
View File
@@ -292,8 +292,6 @@ module DiscourseAi
llm_model =
if model.is_a?(LlmModel)
model
elsif model.is_a?(Numeric)
LlmModel.find_by(id: model)
else
model_name_without_prov = model.split(":").last.to_i
@@ -10,9 +10,16 @@ module DiscourseAi
def valid_value?(val)
return true if val == "f"
@llm_dependency_setting_name = :ai_default_llm_model
if @opts[:name] == :ai_summarization_enabled || @opts[:name] == :ai_helper_enabled
has_llms = LlmModel.count > 0
@no_llms_configured = !has_llms
has_llms
else
@llm_dependency_setting_name =
DiscourseAi::Configuration::LlmValidator.new.choose_llm_setting_for(@opts[:name])
SiteSetting.public_send(@llm_dependency_setting_name).present?
SiteSetting.public_send(@llm_dependency_setting_name).present?
end
end
def error_message
+15 -39
View File
@@ -22,53 +22,24 @@ module DiscourseAi
.each { |llm_id, name, id| rval[llm_id] << { type: :ai_persona, name: name, id: id } }
if SiteSetting.ai_helper_enabled
{
"#{I18n.t("js.discourse_ai.features.ai_helper.proofread")}" =>
SiteSetting.ai_helper_proofreader_persona,
"#{I18n.t("js.discourse_ai.features.ai_helper.title_suggestions")}" =>
SiteSetting.ai_helper_title_suggestions_persona,
"#{I18n.t("js.discourse_ai.features.ai_helper.explain")}" =>
SiteSetting.ai_helper_explain_persona,
"#{I18n.t("js.discourse_ai.features.ai_helper.illustrate_post")}" =>
SiteSetting.ai_helper_post_illustrator_persona,
"#{I18n.t("js.discourse_ai.features.ai_helper.smart_dates")}" =>
SiteSetting.ai_helper_smart_dates_persona,
"#{I18n.t("js.discourse_ai.features.ai_helper.translator")}" =>
SiteSetting.ai_helper_translator_persona,
"#{I18n.t("js.discourse_ai.features.ai_helper.markdown_tables")}" =>
SiteSetting.ai_helper_markdown_tables_persona,
"#{I18n.t("js.discourse_ai.features.ai_helper.custom_prompt")}" =>
SiteSetting.ai_helper_custom_prompt_persona,
}.each do |helper_type, persona_id|
next if persona_id.blank?
persona = AiPersona.find_by(id: persona_id)
next if persona.blank? || persona.default_llm_id.blank?
model_id = persona.default_llm_id || SiteSetting.ai_default_llm_model.to_i
rval[model_id] << { type: :ai_helper, name: helper_type }
end
model_id = SiteSetting.ai_helper_model.split(":").last.to_i
rval[model_id] << { type: :ai_helper } if model_id != 0
end
if SiteSetting.ai_helper_enabled_features.split("|").include?("image_caption")
image_caption_persona = AiPersona.find_by(id: SiteSetting.ai_helper_image_caption_persona)
model_id = image_caption_persona.default_llm_id || SiteSetting.ai_default_llm_model.to_i
rval[model_id] << { type: :ai_helper_image_caption }
if SiteSetting.ai_helper_image_caption_model
model_id = SiteSetting.ai_helper_image_caption_model.split(":").last.to_i
rval[model_id] << { type: :ai_helper_image_caption } if model_id != 0
end
if SiteSetting.ai_summarization_enabled
summarization_persona = AiPersona.find_by(id: SiteSetting.ai_summarization_persona)
model_id = summarization_persona.default_llm_id || SiteSetting.ai_default_llm_model.to_i
model_id = summarization_persona.default_llm_id || LlmModel.last&.id
rval[model_id] << { type: :ai_summarization }
end
if SiteSetting.ai_embeddings_semantic_search_enabled
search_persona =
AiPersona.find_by(id: SiteSetting.ai_embeddings_semantic_search_hyde_persona)
model_id = search_persona.default_llm_id || SiteSetting.ai_default_llm_model.to_i
model_id = SiteSetting.ai_embeddings_semantic_search_hyde_model.split(":").last.to_i
rval[model_id] << { type: :ai_embeddings_semantic_search }
end
@@ -87,8 +58,12 @@ module DiscourseAi
)
.each do |model_text, name, id|
next if model_text.blank?
model_id = model_text.to_i
rval[model_id] << { type: :automation, name: name, id: id } if model_id.present?
model_id = model_text.split("custom:").last.to_i
if model_id.present?
if model_text =~ /custom:(\d+)/
rval[model_id] << { type: :automation, name: name, id: id }
end
end
end
end
@@ -119,7 +94,7 @@ module DiscourseAi
builder.query_hash.map(&:symbolize_keys)
end
def self.values(allowed_seeded_llms: [])
def self.values(allowed_seeded_llms: nil)
values = DB.query_hash(<<~SQL).map(&:symbolize_keys)
SELECT display_name AS name, id AS value
FROM llm_models
@@ -132,6 +107,7 @@ module DiscourseAi
end
end
values.each { |value_h| value_h[:value] = "custom:#{value_h[:value]}" }
values
end
end

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