Compare commits
8
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
415c1156e0 | ||
|
|
c312b3184a | ||
|
|
f231aad8b5 | ||
|
|
cc77e73cfd | ||
|
|
1d88ca44f0 | ||
|
|
6059b6e111 | ||
|
|
5d80a34589 | ||
|
|
8630bc145e |
@@ -1,3 +1,4 @@
|
||||
< 3.5.0.beta8-dev: 5d80a34589d63e381d80273828072badc18955b1
|
||||
< 3.5.0.beta7-dev: cd14b0c0bee0cf63c59b64b6f7213e31a37f11a7
|
||||
< 3.5.0.beta6-dev: 3e74eea1e5e3143888d67a8d8a11206df214dc24
|
||||
< 3.5.0.beta3-dev: 09a68414804a1447f52e5d60691ba59742cda9ec
|
||||
|
||||
+1
-1
@@ -34,7 +34,7 @@ GEM
|
||||
racc
|
||||
prettier_print (1.2.1)
|
||||
racc (1.8.1)
|
||||
rack (3.1.16)
|
||||
rack (3.1.18)
|
||||
rainbow (3.1.1)
|
||||
regexp_parser (2.10.0)
|
||||
rubocop (1.74.0)
|
||||
|
||||
@@ -1,15 +1,18 @@
|
||||
# **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
|
||||
|
||||
### Evals
|
||||
|
||||
The directory `evals` contains AI evals for the Discourse AI plugin.
|
||||
The directory `evals` contains AI evals for the Discourse AI plugin.
|
||||
You may create a local config by copying `config/eval-llms.yml` to `config/eval-llms.local.yml` and modifying the values.
|
||||
|
||||
To run them use:
|
||||
To run them use:
|
||||
|
||||
cd evals
|
||||
./run --help
|
||||
|
||||
@@ -40,7 +40,7 @@ module Jobs
|
||||
return if locales.blank?
|
||||
|
||||
locales.each do |locale|
|
||||
next if DiscourseAi::Translation::LocaleNormalizer.is_same?(locale, detected_locale)
|
||||
next if LocaleNormalizer.is_same?(locale, detected_locale)
|
||||
regionless_locale = locale.split("_").first
|
||||
next if post.post_localizations.where("locale LIKE ?", "#{regionless_locale}%").exists?
|
||||
|
||||
|
||||
@@ -38,7 +38,7 @@ module Jobs
|
||||
return if locales.blank?
|
||||
|
||||
locales.each do |locale|
|
||||
next if DiscourseAi::Translation::LocaleNormalizer.is_same?(locale, detected_locale)
|
||||
next if LocaleNormalizer.is_same?(locale, detected_locale)
|
||||
regionless_locale = locale.split("_").first
|
||||
next if topic.topic_localizations.where("locale LIKE ?", "#{regionless_locale}%").exists?
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ module Jobs
|
||||
missing_locales = locales - existing_locales - [category.locale]
|
||||
missing_locales.each do |locale|
|
||||
break if remaining_limit <= 0
|
||||
next if DiscourseAi::Translation::LocaleNormalizer.is_same?(locale, category.locale)
|
||||
next if LocaleNormalizer.is_same?(locale, category.locale)
|
||||
|
||||
begin
|
||||
DiscourseAi::Translation::CategoryLocalizer.localize(category, locale)
|
||||
|
||||
@@ -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?
|
||||
|
||||
|
||||
@@ -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>
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -180,14 +180,13 @@ en:
|
||||
temperature:
|
||||
label: "Temperature"
|
||||
description: "Temperature to use for the LLM. Increase to increase randomness (leave empty to use model default)"
|
||||
max_output_tokens:
|
||||
label: "Max output tokens"
|
||||
description: "When specified, sets an upper bound to the maximum number of tokens the model can generate. Respects LLM's max output tokens limit"
|
||||
|
||||
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 +569,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"
|
||||
|
||||
@@ -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"
|
||||
|
||||
+11
-13
@@ -1,6 +1,6 @@
|
||||
discourse_ai:
|
||||
discourse_ai_enabled:
|
||||
default: true
|
||||
default: false
|
||||
client: true
|
||||
ai_artifact_security:
|
||||
client: true
|
||||
@@ -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"
|
||||
@@ -531,6 +527,8 @@ discourse_ai:
|
||||
area: "ai-features/translation"
|
||||
ai_translation_backfill_max_age_days:
|
||||
default: 5
|
||||
min: 0
|
||||
max: 20000
|
||||
client: false
|
||||
area: "ai-features/translation"
|
||||
ai_translation_verbose_logs:
|
||||
|
||||
@@ -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,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
|
||||
@@ -0,0 +1,22 @@
|
||||
# frozen_string_literal: true
|
||||
class SetTranslationBackfillMaxAge < ActiveRecord::Migration[7.2]
|
||||
def up
|
||||
execute <<~SQL
|
||||
UPDATE site_settings
|
||||
SET value = '20000'
|
||||
WHERE name = 'ai_translation_backfill_max_age_days'
|
||||
AND value::integer > 20000;
|
||||
SQL
|
||||
|
||||
execute <<~SQL
|
||||
UPDATE site_settings
|
||||
SET value = '0'
|
||||
WHERE name = 'ai_translation_backfill_max_age_days'
|
||||
AND value::integer < 0;
|
||||
SQL
|
||||
end
|
||||
|
||||
def down
|
||||
raise ActiveRecord::IrreversibleMigration
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,23 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class EnableAiIfAlreadyInstalled < ActiveRecord::Migration[7.2]
|
||||
def up
|
||||
installed_at = DB.query_single(<<~SQL)&.first
|
||||
SELECT created_at FROM schema_migration_details WHERE version='20230224165056'
|
||||
SQL
|
||||
|
||||
if installed_at && installed_at < 1.hour.ago
|
||||
# The plugin was installed before we changed it to be disabled-by-default
|
||||
# Therefore, if there is no existing database value, enable the plugin
|
||||
execute <<~SQL
|
||||
INSERT INTO site_settings(name, data_type, value, created_at, updated_at)
|
||||
VALUES('discourse_ai_enabled', 5, 't', NOW(), NOW())
|
||||
ON CONFLICT (name) DO NOTHING
|
||||
SQL
|
||||
end
|
||||
end
|
||||
|
||||
def down
|
||||
raise ActiveRecord::IrreversibleMigration
|
||||
end
|
||||
end
|
||||
@@ -0,0 +1,11 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class AddIndexToAiTopicsEmbeddings < ActiveRecord::Migration[7.2]
|
||||
def up
|
||||
add_index :ai_topics_embeddings, %i[topic_id model_id]
|
||||
end
|
||||
|
||||
def down
|
||||
raise ActiveRecord::IrreversibleMigration
|
||||
end
|
||||
end
|
||||
@@ -24,6 +24,7 @@ if defined?(DiscourseAutomation)
|
||||
field :max_post_tokens, component: :text
|
||||
field :stop_sequences, component: :text_list, required: false
|
||||
field :temperature, component: :text
|
||||
field :max_output_tokens, component: :text
|
||||
|
||||
# Actions
|
||||
field :category, component: :category
|
||||
@@ -85,6 +86,9 @@ if defined?(DiscourseAutomation)
|
||||
temperature = temperature.to_f
|
||||
end
|
||||
|
||||
max_output_tokens = fields.dig("max_output_tokens", "value").to_i
|
||||
max_output_tokens = nil if max_output_tokens <= 0
|
||||
|
||||
max_post_tokens = nil if max_post_tokens <= 0
|
||||
|
||||
stop_sequences = fields.dig("stop_sequences", "value")
|
||||
@@ -122,6 +126,7 @@ if defined?(DiscourseAutomation)
|
||||
stop_sequences: stop_sequences,
|
||||
automation: self.automation,
|
||||
temperature: temperature,
|
||||
max_output_tokens: max_output_tokens,
|
||||
action: context["action"],
|
||||
)
|
||||
rescue => e
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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>"],
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user