Compare commits
28
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a84e253c5b | ||
|
|
fdadfa029e | ||
|
|
94f6c632bf | ||
|
|
41054c4fb8 | ||
|
|
943504049c | ||
|
|
a08d168740 | ||
|
|
0687ec75c3 | ||
|
|
72607c3560 | ||
|
|
6af415f7f0 | ||
|
|
1b209f5fb1 | ||
|
|
50c2d56aff | ||
|
|
f148452f4c | ||
|
|
c6aeabbfc0 | ||
|
|
eac83eb619 | ||
|
|
9019e90b87 | ||
|
|
3c7bd9bbd3 | ||
|
|
324af23b72 | ||
|
|
da9e192949 | ||
|
|
10dae65740 | ||
|
|
64641b6175 | ||
|
|
97fc822cb6 | ||
|
|
c0328267be | ||
|
|
dd63ade348 | ||
|
|
4a40531143 | ||
|
|
bfe3b1c3b8 | ||
|
|
f789d3ee96 | ||
|
|
867cd54556 | ||
|
|
23b88537d9 |
+3
-1
@@ -4,7 +4,9 @@ export default DiscourseRoute.extend({
|
||||
async model(params) {
|
||||
const allLlms = this.modelFor("adminPlugins.show.discourse-ai-llms");
|
||||
const id = parseInt(params.id, 10);
|
||||
return allLlms.findBy("id", id);
|
||||
const record = allLlms.findBy("id", id);
|
||||
record.provider_params = record.provider_params || {};
|
||||
return record;
|
||||
},
|
||||
|
||||
setupController(controller, model) {
|
||||
|
||||
@@ -45,6 +45,10 @@ module DiscourseAi
|
||||
def update
|
||||
llm_model = LlmModel.find(params[:id])
|
||||
|
||||
if llm_model.seeded?
|
||||
return render_json_error(I18n.t("discourse_ai.llm.cannot_edit_builtin"), status: 403)
|
||||
end
|
||||
|
||||
if llm_model.update(ai_llm_params(updating: llm_model))
|
||||
llm_model.toggle_companion_user
|
||||
render json: LlmModelSerializer.new(llm_model)
|
||||
@@ -56,6 +60,10 @@ module DiscourseAi
|
||||
def destroy
|
||||
llm_model = LlmModel.find(params[:id])
|
||||
|
||||
if llm_model.seeded?
|
||||
return render_json_error(I18n.t("discourse_ai.llm.cannot_delete_builtin"), status: 403)
|
||||
end
|
||||
|
||||
in_use_by = DiscourseAi::Configuration::LlmValidator.new.modules_using(llm_model)
|
||||
|
||||
if !in_use_by.empty?
|
||||
@@ -117,11 +125,21 @@ module DiscourseAi
|
||||
new_url = params.dig(:ai_llm, :url)
|
||||
permitted[:url] = new_url if permit_url && new_url
|
||||
|
||||
extra_field_names = LlmModel.provider_params.dig(provider&.to_sym, :fields).to_a
|
||||
received_prov_params = params.dig(:ai_llm, :provider_params)
|
||||
permitted[:provider_params] = received_prov_params.slice(
|
||||
*extra_field_names,
|
||||
).permit! if !extra_field_names.empty? && received_prov_params.present?
|
||||
extra_field_names = LlmModel.provider_params.dig(provider&.to_sym)
|
||||
if extra_field_names.present?
|
||||
received_prov_params =
|
||||
params.dig(:ai_llm, :provider_params)&.slice(*extra_field_names.keys)
|
||||
|
||||
if received_prov_params.present?
|
||||
received_prov_params.each do |pname, value|
|
||||
if extra_field_names[pname.to_sym] == :checkbox
|
||||
received_prov_params[pname] = ActiveModel::Type::Boolean.new.cast(value)
|
||||
end
|
||||
end
|
||||
|
||||
permitted[:provider_params] = received_prov_params.permit!
|
||||
end
|
||||
end
|
||||
|
||||
permitted
|
||||
end
|
||||
|
||||
@@ -9,6 +9,7 @@ module DiscourseAi
|
||||
|
||||
def search
|
||||
query = params[:q].to_s
|
||||
skip_hyde = params[:hyde].downcase.to_s == "false" || params[:hyde].to_s == "0"
|
||||
|
||||
if query.length < SiteSetting.min_search_term_length
|
||||
raise Discourse::InvalidParameters.new(:q)
|
||||
@@ -31,7 +32,7 @@ module DiscourseAi
|
||||
|
||||
hijack do
|
||||
semantic_search
|
||||
.search_for_topics(query)
|
||||
.search_for_topics(query, _page = 1, hyde: !skip_hyde)
|
||||
.each { |topic_post| grouped_results.add(topic_post) }
|
||||
|
||||
render_serialized(grouped_results, GroupedSearchResultSerializer, result: grouped_results)
|
||||
@@ -39,6 +40,9 @@ module DiscourseAi
|
||||
end
|
||||
|
||||
def quick_search
|
||||
# this search function searches posts (vs: topics)
|
||||
# it requires post embeddings and a reranker
|
||||
# it will not perform a hyde expantion
|
||||
query = params[:q].to_s
|
||||
|
||||
if query.length < SiteSetting.min_search_term_length
|
||||
|
||||
@@ -12,6 +12,13 @@ module ::Jobs
|
||||
return unless post.topic.custom_fields[DiscourseAi::AiBot::EntryPoint::REQUIRE_TITLE_UPDATE]
|
||||
|
||||
DiscourseAi::AiBot::Playground.new(bot).title_playground(post)
|
||||
|
||||
publish_update(post.topic, { title: post.topic.title })
|
||||
end
|
||||
|
||||
def publish_update(topic, payload)
|
||||
allowed_users = topic.topic_allowed_users.pluck(:user_id)
|
||||
MessageBus.publish("/discourse-ai/ai-bot/topic/#{topic.id}", payload, user_ids: allowed_users)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
+16
-4
@@ -17,12 +17,20 @@ class LlmModel < ActiveRecord::Base
|
||||
def self.provider_params
|
||||
{
|
||||
aws_bedrock: {
|
||||
url_editable: false,
|
||||
fields: %i[access_key_id region],
|
||||
access_key_id: :text,
|
||||
region: :text,
|
||||
},
|
||||
open_ai: {
|
||||
url_editable: true,
|
||||
fields: %i[organization],
|
||||
organization: :text,
|
||||
},
|
||||
hugging_face: {
|
||||
disable_system_prompt: :checkbox,
|
||||
},
|
||||
vllm: {
|
||||
disable_system_prompt: :checkbox,
|
||||
},
|
||||
ollama: {
|
||||
disable_system_prompt: :checkbox,
|
||||
},
|
||||
}
|
||||
end
|
||||
@@ -81,6 +89,10 @@ class LlmModel < ActiveRecord::Base
|
||||
provider_params&.dig(key)
|
||||
end
|
||||
|
||||
def seeded?
|
||||
id < 0
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def required_provider_params
|
||||
|
||||
@@ -21,4 +21,16 @@ class LlmModelSerializer < ApplicationSerializer
|
||||
def used_by
|
||||
DiscourseAi::Configuration::LlmValidator.new.modules_using(object)
|
||||
end
|
||||
|
||||
def api_key
|
||||
object.seeded? ? "********" : object.api_key
|
||||
end
|
||||
|
||||
def url
|
||||
object.seeded? ? "********" : object.url
|
||||
end
|
||||
|
||||
def provider
|
||||
object.seeded? ? "CDCK" : object.provider
|
||||
end
|
||||
end
|
||||
|
||||
@@ -20,26 +20,25 @@ export default class AiTool extends RestModel {
|
||||
return this.getProperties(CREATE_ATTRIBUTES);
|
||||
}
|
||||
|
||||
workingCopy() {
|
||||
const attrs = this.getProperties(CREATE_ATTRIBUTES);
|
||||
|
||||
attrs.parameters = new TrackedArray(
|
||||
attrs.parameters?.map((p) => {
|
||||
trackParameters(parameters) {
|
||||
return new TrackedArray(
|
||||
parameters?.map((p) => {
|
||||
const parameter = new TrackedObject(p);
|
||||
|
||||
//Backwards-compatibility code.
|
||||
// TODO(roman): Remove aug 2024. Leave only else clause.
|
||||
if (parameter.enum_values) {
|
||||
parameter.enum = new TrackedArray(parameter.enum_values);
|
||||
delete parameter.enum_values;
|
||||
} else {
|
||||
if (parameter.enum && parameter.enum.length) {
|
||||
parameter.enum = new TrackedArray(parameter.enum);
|
||||
} else {
|
||||
parameter.enum = null;
|
||||
}
|
||||
|
||||
return parameter;
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
workingCopy() {
|
||||
const attrs = this.getProperties(CREATE_ATTRIBUTES);
|
||||
attrs.parameters = this.trackParameters(attrs.parameters);
|
||||
return this.store.createRecord("ai-tool", attrs);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import Component from "@glimmer/component";
|
||||
import { tracked } from "@glimmer/tracking";
|
||||
import { fn } from "@ember/helper";
|
||||
import { action } from "@ember/object";
|
||||
import { service } from "@ember/service";
|
||||
import { modifier } from "ember-modifier";
|
||||
@@ -17,6 +16,7 @@ import ModalDiffModal from "../components/modal/diff-modal";
|
||||
import ThumbnailSuggestion from "../components/modal/thumbnail-suggestions";
|
||||
|
||||
export default class AiComposerHelperMenu extends Component {
|
||||
@service modal;
|
||||
@service siteSettings;
|
||||
@service aiComposerHelper;
|
||||
@service currentUser;
|
||||
@@ -29,7 +29,6 @@ export default class AiComposerHelperMenu extends Component {
|
||||
@tracked lastUsedOption = null;
|
||||
@tracked thumbnailSuggestions = null;
|
||||
@tracked showThumbnailModal = false;
|
||||
@tracked showDiffModal = false;
|
||||
@tracked lastSelectionRange = null;
|
||||
MENU_STATES = this.aiComposerHelper.MENU_STATES;
|
||||
prompts = [];
|
||||
@@ -100,7 +99,16 @@ export default class AiComposerHelperMenu extends Component {
|
||||
{
|
||||
icon: "exchange-alt",
|
||||
label: "discourse_ai.ai_helper.context_menu.view_changes",
|
||||
action: () => (this.showDiffModal = true),
|
||||
action: () =>
|
||||
this.modal.show(ModalDiffModal, {
|
||||
model: {
|
||||
diff: this.diff,
|
||||
oldValue: this.initialValue,
|
||||
newValue: this.newSelectedText,
|
||||
revert: this.undoAiAction,
|
||||
confirm: () => this.updateMenuState(this.MENU_STATES.resets),
|
||||
},
|
||||
}),
|
||||
classes: "view-changes",
|
||||
},
|
||||
{
|
||||
@@ -154,6 +162,14 @@ export default class AiComposerHelperMenu extends Component {
|
||||
return true;
|
||||
}
|
||||
|
||||
get isExpanded() {
|
||||
if (this.aiComposerHelper.menuState === this.MENU_STATES.triggers) {
|
||||
return "";
|
||||
}
|
||||
|
||||
return "is-expanded";
|
||||
}
|
||||
|
||||
@bind
|
||||
onKeyDown(event) {
|
||||
if (event.key === "Escape") {
|
||||
@@ -202,8 +218,12 @@ export default class AiComposerHelperMenu extends Component {
|
||||
if (option.name === "illustrate_post") {
|
||||
this._toggleLoadingState(false);
|
||||
this.closeMenu();
|
||||
this.showThumbnailModal = true;
|
||||
this.thumbnailSuggestions = data.thumbnails;
|
||||
this.modal.show(ThumbnailSuggestion, {
|
||||
model: {
|
||||
thumbnails: this.thumbnailSuggestions,
|
||||
},
|
||||
});
|
||||
} else {
|
||||
this._updateSuggestedByAi(data);
|
||||
}
|
||||
@@ -294,7 +314,10 @@ export default class AiComposerHelperMenu extends Component {
|
||||
}
|
||||
|
||||
<template>
|
||||
<div class="ai-composer-helper-menu" {{this.documentListeners}}>
|
||||
<div
|
||||
class="ai-composer-helper-menu {{this.isExpanded}}"
|
||||
{{this.documentListeners}}
|
||||
>
|
||||
{{#if (eq this.aiComposerHelper.menuState this.MENU_STATES.triggers)}}
|
||||
<ul class="ai-composer-helper-menu__triggers">
|
||||
<li>
|
||||
@@ -326,26 +349,5 @@ export default class AiComposerHelperMenu extends Component {
|
||||
/>
|
||||
{{/if}}
|
||||
</div>
|
||||
|
||||
{{#if this.showDiffModal}}
|
||||
<ModalDiffModal
|
||||
@confirm={{fn
|
||||
(mut this.aiComposerHelper.menuState)
|
||||
this.MENU_STATES.resets
|
||||
}}
|
||||
@diff={{this.diff}}
|
||||
@oldValue={{this.initialValue}}
|
||||
@newValue={{this.newSelectedText}}
|
||||
@revert={{this.undoAiAction}}
|
||||
@closeModal={{fn (mut this.showDiffModal) false}}
|
||||
/>
|
||||
{{/if}}
|
||||
|
||||
{{#if this.showThumbnailModal}}
|
||||
<ThumbnailSuggestion
|
||||
@thumbnails={{this.thumbnailSuggestions}}
|
||||
@closeModal={{fn (mut this.showThumbnailModal) false}}
|
||||
/>
|
||||
{{/if}}
|
||||
</template>
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import { action, computed } from "@ember/object";
|
||||
import { LinkTo } from "@ember/routing";
|
||||
import { later } from "@ember/runloop";
|
||||
import { inject as service } from "@ember/service";
|
||||
import { eq } from "truth-helpers";
|
||||
import DButton from "discourse/components/d-button";
|
||||
import DToggleSwitch from "discourse/components/d-toggle-switch";
|
||||
import Avatar from "discourse/helpers/bound-avatar-template";
|
||||
@@ -52,15 +53,19 @@ export default class AiLlmEditorForm extends Component {
|
||||
return this.testRunning || this.testResult !== null;
|
||||
}
|
||||
|
||||
@computed("args.model.provider")
|
||||
get canEditURL() {
|
||||
// Explicitly false.
|
||||
return this.metaProviderParams.url_editable !== false;
|
||||
return this.args.model.provider !== "aws_bedrock";
|
||||
}
|
||||
|
||||
get modulesUsingModel() {
|
||||
return this.args.model.used_by?.join(", ");
|
||||
}
|
||||
|
||||
get seeded() {
|
||||
return this.args.model.id < 0;
|
||||
}
|
||||
|
||||
get inUseWarning() {
|
||||
return I18n.t("discourse_ai.llms.in_use_warning", {
|
||||
settings: this.modulesUsingModel,
|
||||
@@ -169,13 +174,19 @@ export default class AiLlmEditorForm extends Component {
|
||||
}
|
||||
|
||||
<template>
|
||||
{{#if this.seeded}}
|
||||
<div class="alert alert-info">
|
||||
{{icon "exclamation-circle"}}
|
||||
{{i18n "discourse_ai.llms.seeded_warning"}}
|
||||
</div>
|
||||
{{/if}}
|
||||
{{#if this.modulesUsingModel}}
|
||||
<div class="alert alert-info">
|
||||
{{icon "exclamation-circle"}}
|
||||
{{this.inUseWarning}}
|
||||
</div>
|
||||
{{/if}}
|
||||
<form class="form-horizontal ai-llm-editor">
|
||||
<form class="form-horizontal ai-llm-editor {{if this.seeded 'seeded'}}">
|
||||
<div class="control-group">
|
||||
<label>{{i18n "discourse_ai.llms.display_name"}}</label>
|
||||
<Input
|
||||
@@ -201,123 +212,136 @@ export default class AiLlmEditorForm extends Component {
|
||||
<ComboBox
|
||||
@value={{@model.provider}}
|
||||
@content={{this.selectedProviders}}
|
||||
@class="ai-llm-editor__provider"
|
||||
/>
|
||||
</div>
|
||||
{{#if this.canEditURL}}
|
||||
{{#unless this.seeded}}
|
||||
{{#if this.canEditURL}}
|
||||
<div class="control-group">
|
||||
<label>{{I18n.t "discourse_ai.llms.url"}}</label>
|
||||
<Input
|
||||
class="ai-llm-editor-input ai-llm-editor__url"
|
||||
@type="text"
|
||||
@value={{@model.url}}
|
||||
required="true"
|
||||
/>
|
||||
</div>
|
||||
{{/if}}
|
||||
<div class="control-group">
|
||||
<label>{{I18n.t "discourse_ai.llms.url"}}</label>
|
||||
<label>{{I18n.t "discourse_ai.llms.api_key"}}</label>
|
||||
<div class="ai-llm-editor__secret-api-key-group">
|
||||
<Input
|
||||
@value={{@model.api_key}}
|
||||
class="ai-llm-editor-input ai-llm-editor__api-key"
|
||||
@type={{if this.apiKeySecret "password" "text"}}
|
||||
required="true"
|
||||
{{on "focusout" this.makeApiKeySecret}}
|
||||
/>
|
||||
<DButton
|
||||
@action={{this.toggleApiKeySecret}}
|
||||
@icon="far-eye-slash"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
{{#each-in this.metaProviderParams as |field type|}}
|
||||
<div class="control-group ai-llm-editor-provider-param__{{type}}">
|
||||
<label>{{I18n.t
|
||||
(concat "discourse_ai.llms.provider_fields." field)
|
||||
}}</label>
|
||||
{{#if (eq type "checkbox")}}
|
||||
<Input
|
||||
@type={{type}}
|
||||
@checked={{mut (get @model.provider_params field)}}
|
||||
/>
|
||||
{{else}}
|
||||
<Input
|
||||
@type={{type}}
|
||||
@value={{mut (get @model.provider_params field)}}
|
||||
/>
|
||||
{{/if}}
|
||||
</div>
|
||||
{{/each-in}}
|
||||
<div class="control-group">
|
||||
<label>{{I18n.t "discourse_ai.llms.tokenizer"}}</label>
|
||||
<ComboBox
|
||||
@value={{@model.tokenizer}}
|
||||
@content={{@llms.resultSetMeta.tokenizers}}
|
||||
@class="ai-llm-editor__tokenizer"
|
||||
/>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<label>{{i18n "discourse_ai.llms.max_prompt_tokens"}}</label>
|
||||
<Input
|
||||
class="ai-llm-editor-input ai-llm-editor__url"
|
||||
@type="text"
|
||||
@value={{@model.url}}
|
||||
@type="number"
|
||||
class="ai-llm-editor-input ai-llm-editor__max-prompt-tokens"
|
||||
step="any"
|
||||
min="0"
|
||||
lang="en"
|
||||
@value={{@model.max_prompt_tokens}}
|
||||
required="true"
|
||||
/>
|
||||
</div>
|
||||
{{/if}}
|
||||
<div class="control-group">
|
||||
<label>{{I18n.t "discourse_ai.llms.api_key"}}</label>
|
||||
<div class="ai-llm-editor__secret-api-key-group">
|
||||
<Input
|
||||
@value={{@model.api_key}}
|
||||
class="ai-llm-editor-input ai-llm-editor__api-key"
|
||||
@type={{if this.apiKeySecret "password" "text"}}
|
||||
required="true"
|
||||
{{on "focusout" this.makeApiKeySecret}}
|
||||
/>
|
||||
<DButton @action={{this.toggleApiKeySecret}} @icon="far-eye-slash" />
|
||||
</div>
|
||||
</div>
|
||||
{{#each this.metaProviderParams.fields as |field|}}
|
||||
<div class="control-group">
|
||||
<label>{{I18n.t
|
||||
(concat "discourse_ai.llms.provider_fields." field)
|
||||
}}</label>
|
||||
<Input
|
||||
@type="text"
|
||||
@value={{mut (get @model.provider_params field)}}
|
||||
class="ai-llm-editor-input ai-llm-editor__{{field}}"
|
||||
<DTooltip
|
||||
@icon="question-circle"
|
||||
@content={{I18n.t "discourse_ai.llms.hints.max_prompt_tokens"}}
|
||||
/>
|
||||
</div>
|
||||
{{/each}}
|
||||
<div class="control-group">
|
||||
<label>{{I18n.t "discourse_ai.llms.tokenizer"}}</label>
|
||||
<ComboBox
|
||||
@value={{@model.tokenizer}}
|
||||
@content={{@llms.resultSetMeta.tokenizers}}
|
||||
/>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<label>{{i18n "discourse_ai.llms.max_prompt_tokens"}}</label>
|
||||
<Input
|
||||
@type="number"
|
||||
class="ai-llm-editor-input ai-llm-editor__max-prompt-tokens"
|
||||
step="any"
|
||||
min="0"
|
||||
lang="en"
|
||||
@value={{@model.max_prompt_tokens}}
|
||||
required="true"
|
||||
/>
|
||||
<DTooltip
|
||||
@icon="question-circle"
|
||||
@content={{I18n.t "discourse_ai.llms.hints.max_prompt_tokens"}}
|
||||
/>
|
||||
</div>
|
||||
<div class="control-group ai-llm-editor__vision-enabled">
|
||||
<Input @type="checkbox" @checked={{@model.vision_enabled}} />
|
||||
<label>{{I18n.t "discourse_ai.llms.vision_enabled"}}</label>
|
||||
<DTooltip
|
||||
@icon="question-circle"
|
||||
@content={{I18n.t "discourse_ai.llms.hints.vision_enabled"}}
|
||||
/>
|
||||
</div>
|
||||
<div class="control-group">
|
||||
<DToggleSwitch
|
||||
class="ai-llm-editor__enabled-chat-bot"
|
||||
@state={{@model.enabled_chat_bot}}
|
||||
@label="discourse_ai.llms.enabled_chat_bot"
|
||||
{{on "click" this.toggleEnabledChatBot}}
|
||||
/>
|
||||
</div>
|
||||
{{#if @model.user}}
|
||||
<div class="control-group">
|
||||
<label>{{i18n "discourse_ai.llms.ai_bot_user"}}</label>
|
||||
<a
|
||||
class="avatar"
|
||||
href={{@model.user.path}}
|
||||
data-user-card={{@model.user.username}}
|
||||
>
|
||||
{{Avatar @model.user.avatar_template "small"}}
|
||||
</a>
|
||||
<LinkTo @route="adminUser" @model={{this.adminUser}}>
|
||||
{{@model.user.username}}
|
||||
</LinkTo>
|
||||
<div class="control-group ai-llm-editor__vision-enabled">
|
||||
<Input @type="checkbox" @checked={{@model.vision_enabled}} />
|
||||
<label>{{I18n.t "discourse_ai.llms.vision_enabled"}}</label>
|
||||
<DTooltip
|
||||
@icon="question-circle"
|
||||
@content={{I18n.t "discourse_ai.llms.hints.vision_enabled"}}
|
||||
/>
|
||||
</div>
|
||||
{{/if}}
|
||||
<div class="control-group ai-llm-editor__action_panel">
|
||||
<DButton
|
||||
class="ai-llm-editor__test"
|
||||
@action={{this.test}}
|
||||
@disabled={{this.testRunning}}
|
||||
>
|
||||
{{I18n.t "discourse_ai.llms.tests.title"}}
|
||||
</DButton>
|
||||
|
||||
<DButton
|
||||
class="btn-primary ai-llm-editor__save"
|
||||
@action={{this.save}}
|
||||
@disabled={{this.isSaving}}
|
||||
>
|
||||
{{I18n.t "discourse_ai.llms.save"}}
|
||||
</DButton>
|
||||
{{#unless @model.isNew}}
|
||||
<div class="control-group">
|
||||
<DToggleSwitch
|
||||
class="ai-llm-editor__enabled-chat-bot"
|
||||
@state={{@model.enabled_chat_bot}}
|
||||
@label="discourse_ai.llms.enabled_chat_bot"
|
||||
{{on "click" this.toggleEnabledChatBot}}
|
||||
/>
|
||||
</div>
|
||||
{{#if @model.user}}
|
||||
<div class="control-group">
|
||||
<label>{{i18n "discourse_ai.llms.ai_bot_user"}}</label>
|
||||
<a
|
||||
class="avatar"
|
||||
href={{@model.user.path}}
|
||||
data-user-card={{@model.user.username}}
|
||||
>
|
||||
{{Avatar @model.user.avatar_template "small"}}
|
||||
</a>
|
||||
<LinkTo @route="adminUser" @model={{this.adminUser}}>
|
||||
{{@model.user.username}}
|
||||
</LinkTo>
|
||||
</div>
|
||||
{{/if}}
|
||||
<div class="control-group ai-llm-editor__action_panel">
|
||||
<DButton
|
||||
@action={{this.delete}}
|
||||
class="btn-danger ai-llm-editor__delete"
|
||||
class="ai-llm-editor__test"
|
||||
@action={{this.test}}
|
||||
@disabled={{this.testRunning}}
|
||||
>
|
||||
{{I18n.t "discourse_ai.llms.delete"}}
|
||||
{{I18n.t "discourse_ai.llms.tests.title"}}
|
||||
</DButton>
|
||||
{{/unless}}
|
||||
</div>
|
||||
|
||||
<DButton
|
||||
class="btn-primary ai-llm-editor__save"
|
||||
@action={{this.save}}
|
||||
@disabled={{this.isSaving}}
|
||||
>
|
||||
{{I18n.t "discourse_ai.llms.save"}}
|
||||
</DButton>
|
||||
{{#unless @model.isNew}}
|
||||
<DButton
|
||||
@action={{this.delete}}
|
||||
class="btn-danger ai-llm-editor__delete"
|
||||
>
|
||||
{{I18n.t "discourse_ai.llms.delete"}}
|
||||
</DButton>
|
||||
{{/unless}}
|
||||
</div>
|
||||
{{/unless}}
|
||||
|
||||
<div class="control-group ai-llm-editor-tests">
|
||||
{{#if this.displayTestResult}}
|
||||
|
||||
@@ -25,6 +25,7 @@ export default class AiToolEditor extends Component {
|
||||
@service dialog;
|
||||
@service modal;
|
||||
@service toasts;
|
||||
@service store;
|
||||
|
||||
@tracked isSaving = false;
|
||||
@tracked editingModel = null;
|
||||
@@ -53,8 +54,9 @@ export default class AiToolEditor extends Component {
|
||||
@action
|
||||
configurePreset() {
|
||||
this.selectedPreset = this.args.presets.findBy("preset_id", this.presetId);
|
||||
this.editingModel = this.args.model.workingCopy();
|
||||
this.editingModel.setProperties(this.selectedPreset);
|
||||
this.editingModel = this.store
|
||||
.createRecord("ai-tool", this.selectedPreset)
|
||||
.workingCopy();
|
||||
this.showDelete = false;
|
||||
}
|
||||
|
||||
@@ -192,7 +194,7 @@ export default class AiToolEditor extends Component {
|
||||
<label>{{I18n.t "discourse_ai.tools.script"}}</label>
|
||||
<AceEditor
|
||||
@content={{this.editingModel.script}}
|
||||
@onChange={{withEventValue (fn (mut this.editingModel.script))}}
|
||||
@onChange={{fn (mut this.editingModel.script)}}
|
||||
@mode={{ACE_EDITOR_MODE}}
|
||||
@theme={{ACE_EDITOR_THEME}}
|
||||
@editorId="ai-tool-script-editor"
|
||||
|
||||
@@ -7,7 +7,6 @@ import DButton from "discourse/components/d-button";
|
||||
import withEventValue from "discourse/helpers/with-event-value";
|
||||
import I18n from "discourse-i18n";
|
||||
import ComboBox from "select-kit/components/combo-box";
|
||||
import and from "truth-helpers/helpers/and";
|
||||
|
||||
const PARAMETER_TYPES = [
|
||||
{ name: "string", id: "string" },
|
||||
@@ -59,6 +58,9 @@ export default class AiToolParameterEditor extends Component {
|
||||
@action
|
||||
removeEnumValue(parameter, index) {
|
||||
parameter.enum.splice(index, 1);
|
||||
if (parameter.enum.length === 0) {
|
||||
parameter.enum = null;
|
||||
}
|
||||
}
|
||||
|
||||
@action
|
||||
@@ -94,6 +96,7 @@ export default class AiToolParameterEditor extends Component {
|
||||
{{on "input" (fn this.toggleRequired parameter)}}
|
||||
checked={{parameter.required}}
|
||||
type="checkbox"
|
||||
class="parameter-row__required-toggle"
|
||||
/>
|
||||
{{I18n.t "discourse_ai.tools.parameter_required"}}
|
||||
</label>
|
||||
@@ -101,8 +104,9 @@ export default class AiToolParameterEditor extends Component {
|
||||
<label>
|
||||
<input
|
||||
{{on "input" (fn this.toggleEnum parameter)}}
|
||||
checked={{and parameter.enum parameter.enum.length}}
|
||||
checked={{parameter.enum}}
|
||||
type="checkbox"
|
||||
class="parameter-row__enum-toggle"
|
||||
/>
|
||||
{{I18n.t "discourse_ai.tools.parameter_enum"}}
|
||||
</label>
|
||||
@@ -114,7 +118,7 @@ export default class AiToolParameterEditor extends Component {
|
||||
/>
|
||||
</div>
|
||||
|
||||
{{#if (and parameter.enum parameter.enum.length)}}
|
||||
{{#if parameter.enum}}
|
||||
<div class="parameter-enum-values">
|
||||
{{#each parameter.enum as |enumValue enumIndex|}}
|
||||
<div class="enum-value-row">
|
||||
|
||||
@@ -1,21 +1,86 @@
|
||||
import Component from "@glimmer/component";
|
||||
import { tracked } from "@glimmer/tracking";
|
||||
import { action } from "@ember/object";
|
||||
import { next } from "@ember/runloop";
|
||||
import { inject as service } from "@ember/service";
|
||||
import { htmlSafe } from "@ember/template";
|
||||
import CookText from "discourse/components/cook-text";
|
||||
import DButton from "discourse/components/d-button";
|
||||
import DModal from "discourse/components/d-modal";
|
||||
import { ajax } from "discourse/lib/ajax";
|
||||
import { popupAjaxError } from "discourse/lib/ajax-error";
|
||||
import i18n from "discourse-common/helpers/i18n";
|
||||
|
||||
export default class ModalDiffModal extends Component {
|
||||
@service currentUser;
|
||||
@tracked loading = false;
|
||||
@tracked diff;
|
||||
suggestion = "";
|
||||
|
||||
PROOFREAD_ID = -303;
|
||||
|
||||
constructor() {
|
||||
super(...arguments);
|
||||
this.diff = this.args.model.diff;
|
||||
|
||||
next(() => {
|
||||
if (this.args.model.toolbarEvent) {
|
||||
this.loadDiff();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
async loadDiff() {
|
||||
this.loading = true;
|
||||
|
||||
try {
|
||||
const suggestion = await ajax("/discourse-ai/ai-helper/suggest", {
|
||||
method: "POST",
|
||||
data: {
|
||||
mode: this.PROOFREAD_ID,
|
||||
text: this.selectedText,
|
||||
force_default_locale: true,
|
||||
},
|
||||
});
|
||||
|
||||
this.diff = suggestion.diff;
|
||||
this.suggestion = suggestion.suggestions[0];
|
||||
} catch (e) {
|
||||
popupAjaxError(e);
|
||||
} finally {
|
||||
this.loading = false;
|
||||
}
|
||||
}
|
||||
|
||||
get selectedText() {
|
||||
const selected = this.args.model.toolbarEvent.selected;
|
||||
|
||||
if (selected.value === "") {
|
||||
return selected.pre + selected.post;
|
||||
}
|
||||
|
||||
return selected.value;
|
||||
}
|
||||
|
||||
@action
|
||||
triggerConfirmChanges() {
|
||||
this.args.closeModal();
|
||||
this.args.confirm();
|
||||
if (this.args.model.confirm) {
|
||||
this.args.model.confirm();
|
||||
}
|
||||
|
||||
if (this.args.model.toolbarEvent && this.suggestion) {
|
||||
this.args.model.toolbarEvent.replaceText(
|
||||
this.selectedText,
|
||||
this.suggestion
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@action
|
||||
triggerRevertChanges() {
|
||||
this.args.model.revert();
|
||||
this.args.closeModal();
|
||||
this.args.revert();
|
||||
}
|
||||
|
||||
<template>
|
||||
@@ -25,30 +90,46 @@ export default class ModalDiffModal extends Component {
|
||||
@closeModal={{@closeModal}}
|
||||
>
|
||||
<:body>
|
||||
{{#if @diff}}
|
||||
{{htmlSafe @diff}}
|
||||
{{#if this.loading}}
|
||||
<div class="composer-ai-helper-modal__loading">
|
||||
<CookText @rawText={{this.selectedText}} />
|
||||
</div>
|
||||
{{else}}
|
||||
<div class="composer-ai-helper-modal__old-value">
|
||||
{{@oldValue}}
|
||||
</div>
|
||||
{{#if this.diff}}
|
||||
{{htmlSafe this.diff}}
|
||||
{{else}}
|
||||
<div class="composer-ai-helper-modal__old-value">
|
||||
{{@model.oldValue}}
|
||||
</div>
|
||||
|
||||
<div class="composer-ai-helper-modal__new-value">
|
||||
{{@newValue}}
|
||||
</div>
|
||||
<div class="composer-ai-helper-modal__new-value">
|
||||
{{@model.newValue}}
|
||||
</div>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
</:body>
|
||||
|
||||
<:footer>
|
||||
<DButton
|
||||
class="btn-primary confirm"
|
||||
@action={{this.triggerConfirmChanges}}
|
||||
@label="discourse_ai.ai_helper.context_menu.confirm"
|
||||
/>
|
||||
<DButton
|
||||
class="btn-flat"
|
||||
@action={{this.triggerRevertChanges}}
|
||||
@label="discourse_ai.ai_helper.context_menu.revert"
|
||||
/>
|
||||
{{#if this.loading}}
|
||||
<DButton
|
||||
class="btn-primary"
|
||||
@label="discourse_ai.ai_helper.context_menu.loading"
|
||||
@disabled={{true}}
|
||||
/>
|
||||
{{else}}
|
||||
<DButton
|
||||
class="btn-primary confirm"
|
||||
@action={{this.triggerConfirmChanges}}
|
||||
@label="discourse_ai.ai_helper.context_menu.confirm"
|
||||
/>
|
||||
{{#if @model.revert}}
|
||||
<DButton
|
||||
class="btn-flat revert"
|
||||
@action={{this.triggerRevertChanges}}
|
||||
@label="discourse_ai.ai_helper.context_menu.revert"
|
||||
/>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
</:footer>
|
||||
</DModal>
|
||||
</template>
|
||||
|
||||
@@ -53,7 +53,7 @@ export default class ThumbnailSuggestions extends Component {
|
||||
>
|
||||
<:body>
|
||||
<div class="ai-thumbnail-suggestions">
|
||||
{{#each @thumbnails as |thumbnail|}}
|
||||
{{#each @model.thumbnails as |thumbnail|}}
|
||||
<ThumbnailSuggestionItem
|
||||
@thumbnail={{thumbnail}}
|
||||
@addSelection={{this.addSelection}}
|
||||
|
||||
@@ -14,6 +14,7 @@ export default class AiComposerHelper extends Component {
|
||||
return showComposerAIHelper(outletArgs, helper, "context_menu");
|
||||
}
|
||||
|
||||
@service site;
|
||||
@service menu;
|
||||
@service aiComposerHelper;
|
||||
@tracked caretCoords;
|
||||
@@ -31,6 +32,9 @@ export default class AiComposerHelper extends Component {
|
||||
document.addEventListener("mousedown", this.onMouseDown, { passive: true });
|
||||
document.addEventListener("mouseup", this.onMouseUp, { passive: true });
|
||||
document.addEventListener("selectionchange", this.onSelectionChanged);
|
||||
window.visualViewport.addEventListener("resize", this.onResize, {
|
||||
passive: true,
|
||||
});
|
||||
|
||||
this.dEditorInput = document.querySelector(".d-editor-input");
|
||||
|
||||
@@ -42,6 +46,7 @@ export default class AiComposerHelper extends Component {
|
||||
document.removeEventListener("mousedown", this.onMouseDown);
|
||||
document.removeEventListener("mouseup", this.onMouseUp);
|
||||
document.removeEventListener("selectionchange", this.onSelectionChanged);
|
||||
window.visualViewport.removeEventListener("resize", this.onResize);
|
||||
|
||||
if (this.dEditorInput) {
|
||||
this.dEditorInput.removeEventListener("scroll", this.updatePosition);
|
||||
@@ -54,6 +59,18 @@ export default class AiComposerHelper extends Component {
|
||||
this.menuInstance?.close();
|
||||
}
|
||||
|
||||
@bind
|
||||
onResize() {
|
||||
if (!this.site.mobileView) {
|
||||
return;
|
||||
}
|
||||
|
||||
document.documentElement.style.setProperty(
|
||||
"--mobile-virtual-screen-height",
|
||||
`${window.visualViewport.height}px`
|
||||
);
|
||||
}
|
||||
|
||||
@bind
|
||||
onSelectionChanged() {
|
||||
if (
|
||||
|
||||
@@ -69,8 +69,10 @@ export default class AiSummaryBox extends Component {
|
||||
}
|
||||
|
||||
@bind
|
||||
subscribe(unsubscribe) {
|
||||
if (unsubscribe && this._channel) {
|
||||
subscribe(unsubscribe, [topicId]) {
|
||||
const sameTopicId = this.args.outletArgs.topic.id === topicId;
|
||||
|
||||
if (unsubscribe && this._channel && !sameTopicId) {
|
||||
this.unsubscribe();
|
||||
}
|
||||
const channel = `/discourse-ai/summaries/topic/${this.args.outletArgs.topic.id}`;
|
||||
@@ -175,7 +177,7 @@ export default class AiSummaryBox extends Component {
|
||||
>
|
||||
<DMenu
|
||||
@onShow={{this.generateSummary}}
|
||||
@arrow={{true}}
|
||||
@arrow={{false}}
|
||||
@identifier="topic-map__ai-summary"
|
||||
@onRegisterApi={{this.onRegisterApi}}
|
||||
@interactive={{true}}
|
||||
|
||||
@@ -3,19 +3,26 @@ import { LinkTo } from "@ember/routing";
|
||||
import dIcon from "discourse-common/helpers/d-icon";
|
||||
import i18n from "discourse-common/helpers/i18n";
|
||||
|
||||
function showAiPreferences(user) {
|
||||
function showAiPreferences(user, settings) {
|
||||
// Since we only have one AI related user setting we don't show
|
||||
// AI preferences if these conditions aren't met.
|
||||
// If we add more user settings in the future we can move this
|
||||
// logic to the the specific settings and conditionally show it in the template.
|
||||
return user?.user_allowed_ai_auto_image_captions;
|
||||
const aiHelperEnabledFeatures =
|
||||
settings.ai_helper_enabled_features.split("|");
|
||||
|
||||
return (
|
||||
user?.user_allowed_ai_auto_image_captions &&
|
||||
aiHelperEnabledFeatures.includes("image_caption") &&
|
||||
settings.ai_helper_enabled
|
||||
);
|
||||
}
|
||||
|
||||
export default class AutoImageCaptionSetting extends Component {
|
||||
static shouldRender(outletArgs, helper) {
|
||||
return (
|
||||
helper.siteSettings.discourse_ai_enabled &&
|
||||
showAiPreferences(outletArgs.model)
|
||||
showAiPreferences(outletArgs.model, helper.siteSettings)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -11,6 +11,17 @@ export default class PreferencesAiController extends Controller {
|
||||
@service siteSettings;
|
||||
@tracked saved = false;
|
||||
|
||||
get showAutoImageCaptionSetting() {
|
||||
const aiHelperEnabledFeatures =
|
||||
this.siteSettings.ai_helper_enabled_features.split("|");
|
||||
|
||||
return (
|
||||
this.model?.user_allowed_ai_auto_image_captions &&
|
||||
aiHelperEnabledFeatures.includes("image_caption") &&
|
||||
this.siteSettings.ai_helper_enabled
|
||||
);
|
||||
}
|
||||
|
||||
@action
|
||||
save() {
|
||||
this.saved = false;
|
||||
|
||||
@@ -1,17 +1,24 @@
|
||||
<label class="control-label">{{i18n "discourse_ai.title"}}</label>
|
||||
{{!
|
||||
Later when we have more preferences,
|
||||
move the conditional (showAutoImageCaptionSetting)
|
||||
to be only around the auto-image-caption preference.
|
||||
}}
|
||||
{{#if this.showAutoImageCaptionSetting}}
|
||||
<label class="control-label">{{i18n "discourse_ai.title"}}</label>
|
||||
|
||||
<div class="control-group ai-setting">
|
||||
<PreferenceCheckbox
|
||||
@labelKey="discourse_ai.ai_helper.image_caption.automatic_caption_setting"
|
||||
@checked={{this.model.user_option.auto_image_caption}}
|
||||
data-setting-name="auto-image-caption"
|
||||
class="pref-auto-image-caption"
|
||||
<div class="control-group ai-setting">
|
||||
<PreferenceCheckbox
|
||||
@labelKey="discourse_ai.ai_helper.image_caption.automatic_caption_setting"
|
||||
@checked={{this.model.user_option.auto_image_caption}}
|
||||
data-setting-name="auto-image-caption"
|
||||
class="pref-auto-image-caption"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<SaveControls
|
||||
@id="user_ai_preference_save"
|
||||
@model={{this.model}}
|
||||
@action={{this.save}}
|
||||
@saved={{this.saved}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<SaveControls
|
||||
@id="user_ai_preference_save"
|
||||
@model={{this.model}}
|
||||
@action={{this.save}}
|
||||
@saved={{this.saved}}
|
||||
/>
|
||||
{{/if}}
|
||||
@@ -0,0 +1,50 @@
|
||||
import { withPluginApi } from "discourse/lib/plugin-api";
|
||||
import i18n from "discourse-common/helpers/i18n";
|
||||
import ModalDiffModal from "../discourse/components/modal/diff-modal";
|
||||
|
||||
function initializeProofread(api) {
|
||||
api.addComposerToolbarPopupMenuOption({
|
||||
action: (toolbarEvent) => {
|
||||
const modal = api.container.lookup("service:modal");
|
||||
const composer = api.container.lookup("service:composer");
|
||||
const toasts = api.container.lookup("service:toasts");
|
||||
|
||||
if (composer.model.reply?.length === 0) {
|
||||
toasts.error({
|
||||
duration: 3000,
|
||||
data: {
|
||||
message: i18n("discourse_ai.ai_helper.proofread.no_content_error"),
|
||||
},
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
modal.show(ModalDiffModal, {
|
||||
model: {
|
||||
toolbarEvent,
|
||||
},
|
||||
});
|
||||
},
|
||||
icon: "spell-check",
|
||||
label: "discourse_ai.ai_helper.context_menu.proofread_prompt",
|
||||
shortcut: "ALT+P",
|
||||
condition: () => {
|
||||
const siteSettings = api.container.lookup("service:site-settings");
|
||||
const currentUser = api.getCurrentUser();
|
||||
|
||||
return (
|
||||
siteSettings.ai_helper_enabled && currentUser?.can_use_assistant_in_post
|
||||
);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export default {
|
||||
name: "discourse-ai-helper",
|
||||
|
||||
initialize() {
|
||||
withPluginApi("1.1.0", (api) => {
|
||||
initializeProofread(api);
|
||||
});
|
||||
},
|
||||
};
|
||||
@@ -84,10 +84,10 @@ export default apiInitializer("1.25.0", (api) => {
|
||||
}
|
||||
);
|
||||
|
||||
// Checks if image is small (≤ 0.4 MP)
|
||||
// Checks if image is small (≤ 0.1 MP)
|
||||
function isSmallImage(width, height) {
|
||||
const megapixels = (width * height) / 1000000;
|
||||
return megapixels <= 0.4;
|
||||
return megapixels <= 0.1;
|
||||
}
|
||||
|
||||
function needsImprovedCaption(caption) {
|
||||
@@ -145,6 +145,9 @@ export default apiInitializer("1.25.0", (api) => {
|
||||
}
|
||||
|
||||
const caption = await fetchImageCaption(upload.url, "long_url");
|
||||
if (!caption) {
|
||||
return getUploadMarkdown(upload);
|
||||
}
|
||||
return ``;
|
||||
});
|
||||
|
||||
|
||||
@@ -14,6 +14,18 @@
|
||||
height: 200px;
|
||||
}
|
||||
}
|
||||
@keyframes fadeOpacity {
|
||||
0% {
|
||||
opacity: 1;
|
||||
}
|
||||
100% {
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
|
||||
&__loading {
|
||||
animation: fadeOpacity 1.5s infinite alternate;
|
||||
}
|
||||
|
||||
&__old-value {
|
||||
background-color: var(--danger-low);
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
:root {
|
||||
// Overridden in ai-composer-helper.gjs to adjust for virtual keyboard
|
||||
--mobile-virtual-screen-height: 100svh;
|
||||
}
|
||||
|
||||
.ai-composer-helper-menu:not(.is-expanded) {
|
||||
--composer-helper-menu-padding: 1rem;
|
||||
--composer-helper-menu-trigger-size: 3em;
|
||||
--composer-helper-menu-height: calc(1rem + 3em);
|
||||
|
||||
position: fixed;
|
||||
list-style: none;
|
||||
inset: auto;
|
||||
transform: none;
|
||||
max-width: unset;
|
||||
z-index: z("fullscreen");
|
||||
top: calc(
|
||||
var(--mobile-virtual-screen-height) - var(--composer-helper-menu-height) -
|
||||
env(keyboard-inset-height)
|
||||
);
|
||||
right: 0;
|
||||
padding-right: 1rem;
|
||||
padding-bottom: var(--composer-helper-menu-padding);
|
||||
margin: 0px;
|
||||
width: 100vw;
|
||||
text-align: right;
|
||||
border: none;
|
||||
background: linear-gradient(
|
||||
to bottom,
|
||||
transparent 0,
|
||||
rgba(0, 0, 0, 0.3) 100%
|
||||
);
|
||||
box-shadow: none;
|
||||
|
||||
&.out-of-bounds {
|
||||
visibility: visible;
|
||||
pointer-events: initial;
|
||||
}
|
||||
|
||||
button {
|
||||
background: var(--secondary);
|
||||
border: 1px solid var(--primary-low);
|
||||
border-radius: 100%;
|
||||
box-shadow: 0 0 10px 2px
|
||||
dark-light-choose(
|
||||
rgba(var(--primary-rgb), 0.1),
|
||||
rgba(var(--secondary-rgb), 0.1)
|
||||
);
|
||||
width: var(--composer-helper-menu-trigger-size);
|
||||
height: var(--composer-helper-menu-trigger-size);
|
||||
animation-duration: 0.15s;
|
||||
animation-timing-function: cubic-bezier(0, 0.7, 0.9, 1.2);
|
||||
animation-fill-mode: backwards;
|
||||
animation-name: slide-in;
|
||||
|
||||
&:hover {
|
||||
.discourse-no-touch & {
|
||||
background: var(--secondary);
|
||||
}
|
||||
}
|
||||
|
||||
.d-button-label {
|
||||
display: none;
|
||||
}
|
||||
.d-icon {
|
||||
margin: 0;
|
||||
color: var(--tertiary-high);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.ios-safari-composer-hacks .ai-composer-helper-menu:not(.is-expanded) {
|
||||
top: calc(
|
||||
var(--mobile-virtual-screen-height) - var(--composer-helper-menu-height)
|
||||
);
|
||||
}
|
||||
|
||||
@keyframes slide-in {
|
||||
0% {
|
||||
opacity: 0;
|
||||
transform: translateY(calc(100% + 1rem));
|
||||
}
|
||||
100% {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
@@ -18,6 +18,15 @@
|
||||
width: 350px;
|
||||
}
|
||||
|
||||
.ai-llm-editor-provider-param {
|
||||
&__checkbox {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
flex-direction: row-reverse;
|
||||
justify-content: left;
|
||||
}
|
||||
}
|
||||
|
||||
.fk-d-tooltip__icon {
|
||||
padding-left: 0.25em;
|
||||
color: var(--primary-medium);
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -9,14 +9,9 @@
|
||||
max-width: 470px !important; //overruling JS
|
||||
max-height: calc(100vh - var(--header-offset) - 3rem);
|
||||
|
||||
&__inner-content {
|
||||
max-height: unset;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.ai-summary__header,
|
||||
.ai-summary-box {
|
||||
padding-inline: 1.5rem;
|
||||
padding: 0.75em 1rem;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
@@ -36,9 +31,6 @@
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
&-box {
|
||||
padding-bottom: 1rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
.ai-summary-box {
|
||||
padding: 0.75em 1rem;
|
||||
}
|
||||
@@ -236,11 +236,11 @@ en:
|
||||
back: "Back"
|
||||
confirm_delete: Are you sure you want to delete this model?
|
||||
delete: Delete
|
||||
seeded_warning: "This model is pre-configured on your site and cannot be edited."
|
||||
in_use_warning:
|
||||
one: "This model is currently used by the %{settings} setting. If misconfigured, the feature won't work as expected."
|
||||
other: "This model is currently used by the following settings: %{settings}. If misconfigured, features won't work as expected. "
|
||||
|
||||
|
||||
preconfigured_llms: "Select your LLM"
|
||||
preconfigured:
|
||||
none: "Configure manually..."
|
||||
@@ -268,11 +268,13 @@ en:
|
||||
google: "Google"
|
||||
azure: "Azure"
|
||||
ollama: "Ollama"
|
||||
CDCK: "CDCK"
|
||||
|
||||
provider_fields:
|
||||
access_key_id: "AWS Bedrock Access key ID"
|
||||
region: "AWS Bedrock Region"
|
||||
organization: "Optional OpenAI Organization ID"
|
||||
disable_system_prompt: "Disable system message in prompts"
|
||||
|
||||
related_topics:
|
||||
title: "Related Topics"
|
||||
@@ -293,12 +295,13 @@ en:
|
||||
view_changes: "View Changes"
|
||||
confirm: "Confirm"
|
||||
revert: "Revert"
|
||||
changes: "Changes"
|
||||
changes: "Suggested Edits"
|
||||
custom_prompt:
|
||||
title: "Custom Prompt"
|
||||
placeholder: "Enter a custom prompt..."
|
||||
submit: "Send Prompt"
|
||||
translate_prompt: "Translate to %{language}"
|
||||
proofread_prompt: "Proofread Text"
|
||||
post_options_menu:
|
||||
trigger: "Ask AI"
|
||||
title: "Ask AI"
|
||||
@@ -327,6 +330,8 @@ en:
|
||||
prompt: "This post contains non-captioned images. Would you like to enable automatic AI captions on image uploads? (This can be changed in your preferences later)"
|
||||
confirm: "Enable"
|
||||
cancel: "Don't ask again"
|
||||
proofread:
|
||||
no_content_error: "Unable to proofread text, please add some content to proofread."
|
||||
|
||||
reviewables:
|
||||
model_used: "Model used:"
|
||||
|
||||
@@ -11,6 +11,10 @@ de:
|
||||
title: Beiträge mithilfe von KI sortieren
|
||||
description: "Beiträge mithilfe eines großen Sprachmodells sortieren"
|
||||
system_prompt_missing_post_placeholder: "System-Eingabeaufforderung muss einen Platzhalter für den Beitrag enthalten: %%POST%%"
|
||||
flagged_post: |
|
||||
<div>Antwort des Modells:</div>
|
||||
<p>%%LLM_RESPONSE%%</p>
|
||||
<b>Ausgelöst durch die <a href="/admin/plugins/discourse-automation/%%AUTOMATION_ID%%">%%AUTOMATION_NAME%%</a> Regel.</b>
|
||||
llm_report:
|
||||
title: Regelmäßiger Bericht mit KI
|
||||
description: "Regelmäßiger Bericht auf der Grundlage eines großen Sprachmodells"
|
||||
|
||||
@@ -4,7 +4,10 @@ en:
|
||||
llm_triage:
|
||||
title: Triage posts using AI
|
||||
description: "Triage posts using a large language model"
|
||||
system_prompt_missing_post_placeholder: "System prompt must contain a placeholder for the post: %%POST%%"
|
||||
flagged_post: |
|
||||
<div>Response from the model:</div>
|
||||
<p>%%LLM_RESPONSE%%</p>
|
||||
<b>Triggered by the <a href="/admin/plugins/discourse-automation/%%AUTOMATION_ID%%">%%AUTOMATION_NAME%%</a> rule.</b>
|
||||
llm_report:
|
||||
title: Periodic report using AI
|
||||
description: "Periodic report based on a large language model"
|
||||
@@ -76,6 +79,7 @@ en:
|
||||
ai_summarization_enabled: "Enable the topic summarization module."
|
||||
ai_summarization_model: "Model to use for summarization."
|
||||
ai_custom_summarization_allowed_groups: "Groups allowed to use create new summaries."
|
||||
ai_pm_summarization_allowed_groups: "Groups allowed to create and view summaries in PMs."
|
||||
|
||||
ai_bot_enabled: "Enable the AI Bot module."
|
||||
ai_bot_enable_chat_warning: "Display a warning when PM chat is initiated. Can be overriden by editing the translation string: discourse_ai.ai_bot.pm_warning"
|
||||
@@ -308,6 +312,7 @@ en:
|
||||
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."
|
||||
endpoints:
|
||||
not_configured: "%{display_name} (not configured)"
|
||||
configuration_hint:
|
||||
@@ -317,6 +322,7 @@ en:
|
||||
delete_failed:
|
||||
one: "We couldn't delete this model because %{settings} is using it. Update the setting and try again."
|
||||
other: "We couldn't delete this model because %{settings} are using it. Update the settings and try again."
|
||||
cannot_edit_builtin: "You can't edit a built-in model."
|
||||
|
||||
embeddings:
|
||||
configuration:
|
||||
|
||||
@@ -11,6 +11,10 @@ he:
|
||||
title: מיון ראשוני של פוסטים באמצעות בינה מלאכותית
|
||||
description: "מיון ראשוני של פוסטים באמצעות דגם שפה גדול"
|
||||
system_prompt_missing_post_placeholder: "בקשת המערכת חייבת להכיל ממלא מקום לפוסט: %%POST%%"
|
||||
flagged_post: |
|
||||
<div>תגובה מהמודל:</div>
|
||||
<p>%%LLM_RESPONSE%%</p>
|
||||
<b>הוזנקה על ידי הכלל <a href="/admin/plugins/discourse-automation/%%AUTOMATION_ID%%">%%AUTOMATION_NAME%%</a>.</b>
|
||||
llm_report:
|
||||
title: דוח תקופתי בעזרת בינה מלאכותית
|
||||
description: "דוח תקופתי שמבוסס על דגם שפה גדול"
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user