Compare commits

..
1 Commits
Author SHA1 Message Date
Penar Musaraj 059da39a4a DEV: output raw response in dev mode
Makes it easier to debug issues, for example if rate limits are hit.
2025-05-13 16:54:10 -04:00
118 changed files with 894 additions and 4044 deletions
@@ -42,21 +42,6 @@ module DiscourseAi
<style>
#{artifact.css}
</style>
<script>
window._discourse_user_data = {
#{current_user ? "username: #{current_user.username.to_json}" : "username: null"}
};
window.discourseArtifactReady = new Promise(resolve => {
window._resolveArtifactData = resolve;
});
window.addEventListener('message', function(event) {
if (event.data && event.data.type === 'discourse-artifact-data') {
window.discourseArtifactData = event.data.dataset || {};
Object.assign(window.discourseArtifactData, window._discourse_user_data);
window._resolveArtifactData(window.discourseArtifactData);
}
});
</script>
</head>
<body>
#{artifact.html}
@@ -89,19 +74,6 @@ module DiscourseAi
</head>
<body>
<iframe sandbox="allow-scripts allow-forms" height="100%" width="100%" srcdoc="#{ERB::Util.html_escape(untrusted_html)}" frameborder="0"></iframe>
<script>
document.querySelector('iframe').addEventListener('load', function() {
try {
const iframeWindow = this.contentWindow;
const message = { type: 'discourse-artifact-data', dataset: {} };
if (window.frameElement && window.frameElement.dataset) {
Object.assign(message.dataset, window.frameElement.dataset);
}
iframeWindow.postMessage(message, '*');
} catch (e) { console.error('Error passing data to artifact:', e); }
});
</script>
</body>
</html>
HTML
@@ -8,7 +8,7 @@ module DiscourseAi
before_action :require_site_settings!
skip_before_action :preload_json, :check_xhr, only: %i[show asset]
skip_before_action :redirect_to_login_if_required, :verify_authenticity_token, only: %i[asset]
skip_before_action :verify_authenticity_token, only: ["asset"]
def create
ensure_allowed_create!
@@ -124,10 +124,6 @@ module DiscourseAi
raise Discourse::InvalidParameters.new(:custom_prompt) if params[:custom_prompt].blank?
end
# to stream we must have an appropriate client_id
# otherwise we may end up streaming the data to the wrong client
raise Discourse::InvalidParameters.new(:client_id) if params[:client_id].blank?
if location == "composer"
Jobs.enqueue(
:stream_composer_helper,
@@ -136,7 +132,6 @@ module DiscourseAi
prompt: prompt.name,
custom_prompt: params[:custom_prompt],
force_default_locale: params[:force_default_locale] || false,
client_id: params[:client_id],
)
else
post_id = get_post_param!
@@ -151,7 +146,6 @@ module DiscourseAi
text: text,
prompt: prompt.name,
custom_prompt: params[:custom_prompt],
client_id: params[:client_id],
)
end
@@ -8,7 +8,6 @@ module Jobs
return unless args[:prompt]
return unless user = User.find_by(id: args[:user_id])
return unless args[:text]
return unless args[:client_id]
prompt = CompletionPrompt.enabled_by_name(args[:prompt])
@@ -22,7 +21,6 @@ module Jobs
user,
"/discourse-ai/ai-helper/stream_composer_suggestion",
force_default_locale: args[:force_default_locale],
client_id: args[:client_id],
)
end
end
-1
View File
@@ -266,7 +266,6 @@ class AiPersona < ActiveRecord::Base
define_method(:top_p) { @ai_persona&.top_p }
define_method(:system_prompt) { @ai_persona&.system_prompt || "You are a helpful bot." }
define_method(:uploads) { @ai_persona&.uploads }
define_method(:response_format) { @ai_persona&.response_format }
define_method(:examples) { @ai_persona&.examples }
end
end
@@ -1,7 +1,6 @@
import Component from "@glimmer/component";
import { tracked } from "@glimmer/tracking";
import { action } from "@ember/object";
import didInsert from "@ember/render-modifiers/modifiers/did-insert";
import { service } from "@ember/service";
import DButton from "discourse/components/d-button";
import htmlClass from "discourse/helpers/html-class";
@@ -89,15 +88,6 @@ export default class AiArtifactComponent extends Component {
}`;
}
@action
setDataAttributes(element) {
if (this.args.dataAttributes) {
Object.entries(this.args.dataAttributes).forEach(([key, value]) => {
element.setAttribute(key, value);
});
}
}
<template>
{{#if this.expanded}}
{{htmlClass "ai-artifact-expanded"}}
@@ -128,7 +118,6 @@ export default class AiArtifactComponent extends Component {
src={{this.artifactUrl}}
width="100%"
frameborder="0"
{{didInsert this.setDataAttributes}}
></iframe>
{{/if}}
{{#unless this.requireClickToRun}}
File diff suppressed because it is too large Load Diff
@@ -10,7 +10,6 @@ import { composeAiBotMessage } from "../lib/ai-bot-helper";
import { AI_CONVERSATIONS_PANEL } from "../services/ai-conversations-sidebar-manager";
export default class AiBotHeaderIcon extends Component {
@service appEvents;
@service composer;
@service currentUser;
@service navigationMenu;
@@ -52,7 +51,6 @@ export default class AiBotHeaderIcon extends Component {
}
if (this.siteSettings.ai_bot_enable_dedicated_ux) {
this.appEvents.trigger("discourse-ai:bot-header-icon-clicked");
return this.router.transitionTo("discourse-ai-bot-conversations");
}
@@ -242,7 +242,6 @@ export default class AiPostHelperMenu extends Component {
text: this.args.data.selectedText,
post_id: this.args.data.quoteState.postId,
custom_prompt: this.customPromptValue,
client_id: this.messageBus.clientId,
},
});
@@ -12,7 +12,6 @@ import concatClass from "discourse/helpers/concat-class";
import { ajax } from "discourse/lib/ajax";
import { popupAjaxError } from "discourse/lib/ajax-error";
import { bind } from "discourse/lib/decorators";
import { escapeExpression } from "discourse/lib/utilities";
import { i18n } from "discourse-i18n";
import DiffStreamer from "../../lib/diff-streamer";
import SmoothStreamer from "../../lib/smooth-streamer";
@@ -23,9 +22,7 @@ export default class ModalDiffModal extends Component {
@service messageBus;
@tracked loading = false;
@tracked finalResult = "";
@tracked selectedText = escapeExpression(this.args.model.selectedText);
@tracked diffStreamer = new DiffStreamer(this.selectedText);
@tracked diffStreamer = new DiffStreamer(this.args.model.selectedText);
@tracked suggestion = "";
@tracked
smoothStreamer = new SmoothStreamer(
@@ -38,16 +35,6 @@ export default class ModalDiffModal extends Component {
this.suggestChanges();
}
get diffResult() {
if (this.diffStreamer.diff?.length > 0) {
return this.diffStreamer.diff;
}
// Prevents flash by showing the
// original text when the diff is empty
return this.selectedText;
}
get isStreaming() {
return this.diffStreamer.isStreaming || this.smoothStreamer.isStreaming;
}
@@ -78,14 +65,6 @@ export default class ModalDiffModal extends Component {
async updateResult(result) {
this.loading = false;
if (result.done) {
this.finalResult = result.result;
}
if (result.done) {
this.loading = false;
}
if (this.args.model.showResultAsDiff) {
this.diffStreamer.updateResult(result, "result");
} else {
@@ -105,10 +84,9 @@ export default class ModalDiffModal extends Component {
data: {
location: "composer",
mode: this.args.model.mode,
text: this.selectedText,
text: this.args.model.selectedText,
custom_prompt: this.args.model.customPromptValue,
force_default_locale: true,
client_id: this.messageBus.clientId,
},
});
} catch (e) {
@@ -122,17 +100,16 @@ export default class ModalDiffModal extends Component {
if (this.suggestion) {
this.args.model.toolbarEvent.replaceText(
this.selectedText,
this.args.model.selectedText,
this.suggestion
);
}
const finalResult =
this.finalResult?.length > 0
? this.finalResult
: this.diffStreamer.suggestion;
if (this.args.model.showResultAsDiff && finalResult) {
this.args.model.toolbarEvent.replaceText(this.selectedText, finalResult);
if (this.args.model.showResultAsDiff && this.diffStreamer.suggestion) {
this.args.model.toolbarEvent.replaceText(
this.args.model.selectedText,
this.diffStreamer.suggestion
);
}
}
@@ -143,14 +120,10 @@ export default class ModalDiffModal extends Component {
@closeModal={{@closeModal}}
>
<:body>
<div
{{didInsert this.subscribe}}
{{willDestroy this.unsubscribe}}
class="text-preview"
>
<div {{didInsert this.subscribe}} {{willDestroy this.unsubscribe}}>
{{#if this.loading}}
<div class="composer-ai-helper-modal__loading">
{{~@model.selectedText~}}
<CookText @rawText={{@model.selectedText}} />
</div>
{{else}}
<div
@@ -159,11 +132,10 @@ export default class ModalDiffModal extends Component {
"streamable-content"
(if this.isStreaming "streaming")
(if @model.showResultAsDiff "inline-diff")
(if this.diffStreamer.isThinking "thinking")
}}
>
{{~#if @model.showResultAsDiff~}}
<span class="diff-inner">{{htmlSafe this.diffResult}}</span>
{{#if @model.showResultAsDiff}}
{{htmlSafe this.diffStreamer.diff}}
{{else}}
{{#if this.smoothStreamer.isStreaming}}
<CookText
@@ -7,10 +7,6 @@ import AiSummaryModal from "../../components/modal/ai-summary-modal";
export default class AiSummaryTrigger extends Component {
@service modal;
get isAiConversation() {
return this.args.outletArgs.topic.is_bot_pm;
}
@action
openAiSummaryModal() {
this.modal.show(AiSummaryModal, {
@@ -22,17 +18,15 @@ export default class AiSummaryTrigger extends Component {
}
<template>
{{#unless this.isAiConversation}}
{{#if @outletArgs.topic.summarizable}}
<section class="topic-map__additional-contents toggle-summary">
<DButton
@label="summary.buttons.generate"
@icon="discourse-sparkles"
@action={{this.openAiSummaryModal}}
class="btn-default ai-summarization-button"
/>
</section>
{{/if}}
{{/unless}}
{{#if @outletArgs.topic.summarizable}}
<section class="topic-map__additional-contents toggle-summary">
<DButton
@label="summary.buttons.generate"
@icon="discourse-sparkles"
@action={{this.openAiSummaryModal}}
class="btn-default ai-summarization-button"
/>
</section>
{{/if}}
</template>
}
@@ -1,55 +0,0 @@
import Component from "@glimmer/component";
import { action } from "@ember/object";
import { service } from "@ember/service";
import { or } from "truth-helpers";
import DButton from "discourse/components/d-button";
import Participant from "discourse/components/header/topic/participant";
import AddPmParticipants from "discourse/components/modal/add-pm-participants";
export default class AiConversationInvite extends Component {
static shouldRender(args) {
return args.topic.is_bot_pm;
}
@service site;
@service modal;
@service header;
@service sidebarState;
get participants() {
const participants = [
...this.header.topicInfo.details.allowed_users,
...this.header.topicInfo.details.allowed_groups,
];
return participants;
}
@action
showInvite() {
this.modal.show(AddPmParticipants, {
model: {
title: "discourse_ai.ai_bot.invite_ai_conversation.title",
inviteModel: this.args.outletArgs.topic,
},
});
}
<template>
<div class="ai-conversation__participants">
<DButton
@icon="user-plus"
@label="discourse_ai.ai_bot.invite_ai_conversation.button"
@action={{this.showInvite}}
class="btn-default ai-conversations__invite-button"
/>
{{#each this.participants as |participant|}}
<Participant
@user={{participant}}
@type={{if participant.username "user" "group"}}
@username={{or participant.username participant.name}}
@avatarSize="medium"
/>
{{/each}}
</div>
</template>
}
@@ -0,0 +1,200 @@
import { tracked } from "@glimmer/tracking";
import Controller from "@ember/controller";
import { action } from "@ember/object";
import { getOwner } from "@ember/owner";
import { service } from "@ember/service";
import { TrackedArray } from "@ember-compat/tracked-built-ins";
import { popupAjaxError } from "discourse/lib/ajax-error";
import UppyUpload from "discourse/lib/uppy/uppy-upload";
import UppyMediaOptimization from "discourse/lib/uppy-media-optimization-plugin";
import { clipboardHelpers } from "discourse/lib/utilities";
export default class DiscourseAiBotConversations extends Controller {
@service aiBotConversationsHiddenSubmit;
@service currentUser;
@service mediaOptimizationWorker;
@service site;
@service siteSettings;
@tracked uploads = new TrackedArray();
// Don't track this directly - we'll get it from uppyUpload
textarea = null;
uppyUpload = null;
fileInputEl = null;
_handlePaste = (event) => {
if (document.activeElement !== this.textarea) {
return;
}
const { canUpload, canPasteHtml, types } = clipboardHelpers(event, {
siteSettings: this.siteSettings,
canUpload: true,
});
if (!canUpload || canPasteHtml || types.includes("text/plain")) {
return;
}
if (event && event.clipboardData && event.clipboardData.files) {
this.uppyUpload.addFiles([...event.clipboardData.files], {
pasted: true,
});
}
};
init() {
super.init(...arguments);
this.uppyUpload = new UppyUpload(getOwner(this), {
id: "ai-bot-file-uploader",
type: "ai-bot-conversation",
useMultipartUploadsIfAvailable: true,
uppyReady: () => {
if (this.siteSettings.composer_media_optimization_image_enabled) {
this.uppyUpload.uppyWrapper.useUploadPlugin(UppyMediaOptimization, {
optimizeFn: (data, opts) =>
this.mediaOptimizationWorker.optimizeImage(data, opts),
runParallel: !this.site.isMobileDevice,
});
}
this.uppyUpload.uppyWrapper.onPreProcessProgress((file) => {
const inProgressUpload = this.inProgressUploads?.find(
(upl) => upl.id === file.id
);
if (inProgressUpload && !inProgressUpload.processing) {
inProgressUpload.processing = true;
}
});
this.uppyUpload.uppyWrapper.onPreProcessComplete((file) => {
const inProgressUpload = this.inProgressUploads?.find(
(upl) => upl.id === file.id
);
if (inProgressUpload) {
inProgressUpload.processing = false;
}
});
// Setup paste listener for the textarea
this.textarea?.addEventListener("paste", this._handlePaste);
},
uploadDone: (upload) => {
this.uploads.push(upload);
},
// Fix: Don't try to set inProgressUploads directly
onProgressUploadsChanged: () => {
// This is just for UI triggers - we're already tracking inProgressUploads
this.notifyPropertyChange("inProgressUploads");
},
});
}
willDestroy() {
super.willDestroy(...arguments);
this.textarea?.removeEventListener("paste", this._handlePaste);
this.uppyUpload?.teardown();
}
get loading() {
return this.aiBotConversationsHiddenSubmit?.loading;
}
get inProgressUploads() {
return this.uppyUpload?.inProgressUploads || [];
}
get showUploadsContainer() {
return this.uploads?.length > 0 || this.inProgressUploads?.length > 0;
}
@action
setPersonaId(id) {
this.aiBotConversationsHiddenSubmit.personaId = id;
}
@action
setTargetRecipient(username) {
this.aiBotConversationsHiddenSubmit.targetUsername = username;
}
@action
updateInputValue(value) {
this._autoExpandTextarea();
this.aiBotConversationsHiddenSubmit.inputValue =
value.target?.value || value;
}
@action
handleKeyDown(event) {
if (event.key === "Enter" && !event.shiftKey) {
this.prepareAndSubmitToBot();
}
}
@action
setTextArea(element) {
this.textarea = element;
}
@action
registerFileInput(element) {
if (element) {
this.fileInputEl = element;
if (this.uppyUpload) {
this.uppyUpload.setup(element);
}
}
}
@action
openFileUpload() {
if (this.fileInputEl) {
this.fileInputEl.click();
}
}
@action
removeUpload(upload) {
this.uploads = new TrackedArray(this.uploads.filter((u) => u !== upload));
}
@action
cancelUpload(upload) {
this.uppyUpload.cancelSingleUpload({
fileId: upload.id,
});
}
@action
async prepareAndSubmitToBot() {
// Pass uploads to the service before submitting
this.aiBotConversationsHiddenSubmit.uploads = this.uploads;
try {
await this.aiBotConversationsHiddenSubmit.submitToBot();
this.uploads = new TrackedArray();
} catch (error) {
popupAjaxError(error);
}
}
_autoExpandTextarea() {
this.textarea.style.height = "auto";
this.textarea.style.height = this.textarea.scrollHeight + "px";
// Get the max-height value from CSS (30vh)
const maxHeight = parseInt(getComputedStyle(this.textarea).maxHeight, 10);
// Only enable scrolling if content exceeds max-height
if (this.textarea.scrollHeight > maxHeight) {
this.textarea.style.overflowY = "auto";
} else {
this.textarea.style.overflowY = "hidden";
}
}
}
@@ -2,7 +2,7 @@ import { later } from "@ember/runloop";
import PostUpdater from "./updaters/post-updater";
const PROGRESS_INTERVAL = 40;
const GIVE_UP_INTERVAL = 600000; // 10 minutes which is our max thinking time for now
const GIVE_UP_INTERVAL = 60000;
export const MIN_LETTERS_PER_INTERVAL = 6;
const MAX_FLUSH_TIME = 800;
@@ -12,15 +12,13 @@ export default class PostUpdater extends StreamUpdater {
constructor(postStream, postId) {
super();
this.postStream = postStream;
this.postId = postId;
this.post = postStream.findLoadedPost(postId);
const topicId = postStream.topic.id;
if (this.post) {
this.postElement = document.querySelector(
`.topic-area[data-topic-id="${topicId}"] #post_${this.post.post_number}`
`#post_${this.post.post_number}`
);
}
}
@@ -59,10 +57,6 @@ export default class PostUpdater extends StreamUpdater {
}
async setCooked(value) {
if (!this.postElement) {
return;
}
this.post.set("cooked", value);
(await loadMorphlex()).morphInner(
File diff suppressed because it is too large Load Diff
@@ -5,7 +5,6 @@ import { ADMIN_PANEL, MAIN_PANEL } from "discourse/lib/sidebar/panels";
export const AI_CONVERSATIONS_PANEL = "ai-conversations";
export default class AiConversationsSidebarManager extends Service {
@service appEvents;
@service sidebarState;
@tracked newTopicForceSidebar = false;
@@ -27,7 +26,6 @@ export default class AiConversationsSidebarManager extends Service {
this.sidebarState.isForcingSidebar = true;
document.body.classList.add("has-ai-conversations-sidebar");
this.appEvents.trigger("discourse-ai:force-conversations-sidebar");
return true;
}
@@ -42,7 +40,6 @@ export default class AiConversationsSidebarManager extends Service {
if (this.sidebarState.isForcingSidebar && !isAdminSidebarActive) {
this.sidebarState.setPanel(MAIN_PANEL); // Return to main sidebar panel
this.sidebarState.isForcingSidebar = false;
this.appEvents.trigger("discourse-ai:stop-forcing-conversations-sidebar");
}
}
}
@@ -1,4 +1,102 @@
import { fn, hash } from "@ember/helper";
import { on } from "@ember/modifier";
import didInsert from "@ember/render-modifiers/modifiers/did-insert";
import RouteTemplate from "ember-route-template";
import AiBotConversations from "discourse/plugins/discourse-ai/discourse/components/ai-bot-conversations";
import DButton from "discourse/components/d-button";
import PluginOutlet from "discourse/components/plugin-outlet";
import { i18n } from "discourse-i18n";
import AiPersonaLlmSelector from "discourse/plugins/discourse-ai/discourse/components/ai-persona-llm-selector";
export default RouteTemplate(<template><AiBotConversations /></template>);
export default RouteTemplate(
<template>
<div class="ai-bot-conversations">
<AiPersonaLlmSelector
@showLabels={{true}}
@setPersonaId={{@controller.setPersonaId}}
@setTargetRecipient={{@controller.setTargetRecipient}}
/>
<div class="ai-bot-conversations__content-wrapper">
<div class="ai-bot-conversations__title">
{{i18n "discourse_ai.ai_bot.conversations.header"}}
</div>
<PluginOutlet
@name="ai-bot-conversations-above-input"
@outletArgs={{hash
[email protected]
[email protected]
}}
/>
<div class="ai-bot-conversations__input-wrapper">
<DButton
@icon="upload"
@action={{@controller.openFileUpload}}
@title="discourse_ai.ai_bot.conversations.upload_files"
class="btn btn-transparent ai-bot-upload-btn"
/>
<textarea
{{didInsert @controller.setTextArea}}
{{on "input" @controller.updateInputValue}}
{{on "keydown" @controller.handleKeyDown}}
id="ai-bot-conversations-input"
autofocus="true"
placeholder={{i18n "discourse_ai.ai_bot.conversations.placeholder"}}
minlength="10"
disabled={{@controller.loading}}
rows="1"
/>
<DButton
@action={{@controller.prepareAndSubmitToBot}}
@icon="paper-plane"
@isLoading={{@controller.loading}}
@title="discourse_ai.ai_bot.conversations.header"
class="ai-bot-button btn-transparent ai-conversation-submit"
/>
<input
type="file"
id="ai-bot-file-uploader"
class="hidden-upload-field"
multiple="multiple"
{{didInsert @controller.registerFileInput}}
/>
</div>
<p class="ai-disclaimer">
{{i18n "discourse_ai.ai_bot.conversations.disclaimer"}}
</p>
{{#if @controller.showUploadsContainer}}
<div class="ai-bot-conversations__uploads-container">
{{#each @controller.uploads as |upload|}}
<div class="ai-bot-upload">
<span class="ai-bot-upload__filename">
{{upload.original_filename}}
</span>
<DButton
@icon="xmark"
@action={{fn @controller.removeUpload upload}}
class="btn-transparent ai-bot-upload__remove"
/>
</div>
{{/each}}
{{#each @controller.inProgressUploads as |upload|}}
<div class="ai-bot-upload ai-bot-upload--in-progress">
<span class="ai-bot-upload__filename">{{upload.fileName}}</span>
<span class="ai-bot-upload__progress">
{{upload.progress}}%
</span>
<DButton
@icon="xmark"
@action={{fn @controller.cancelUpload upload}}
class="btn-flat ai-bot-upload__cancel"
/>
</div>
{{/each}}
</div>
{{/if}}
</div>
</div>
</template>
);
@@ -18,24 +18,12 @@ function initializeAiArtifacts(api) {
"data-ai-artifact-version"
);
const dataAttributes = {};
for (const attr of artifactElement.attributes) {
if (
attr.name.startsWith("data-") &&
attr.name !== "data-ai-artifact-id" &&
attr.name !== "data-ai-artifact-version"
) {
dataAttributes[attr.name] = attr.value;
}
}
helper.renderGlimmer(
artifactElement,
<template>
<AiArtifact
@artifactId={{artifactId}}
@artifactVersion={{artifactVersion}}
@dataAttributes={{dataAttributes}}
/>
</template>
);
@@ -30,6 +30,7 @@ export default {
);
const appEvents = api.container.lookup("service:app-events");
const messageBus = api.container.lookup("service:message-bus");
const navigationMenu = api.container.lookup("service:navigationMenu");
api.addSidebarPanel(
(BaseCustomSidebarPanel) =>
@@ -27,7 +27,6 @@ function initializeAiHelperTrigger(api) {
icon: "discourse-sparkles",
title: "discourse_ai.ai_helper.context_menu.trigger",
preventFocus: true,
hideShortcutInTitle: true,
shortcut: "ALT+P",
shortcutAction: (toolbarEvent) => {
if (toolbarEvent.getText().length === 0) {
-49
View File
@@ -79,52 +79,3 @@ article.streaming .cooked {
}
}
}
@keyframes mark-blink {
0%,
100% {
border-color: transparent;
}
50% {
border-color: var(--highlight-high);
}
}
@keyframes fade-in-highlight {
from {
opacity: 0.5;
}
to {
opacity: 1;
}
}
mark.highlight {
background-color: var(--highlight-high);
animation: fade-in-highlight 0.5s ease-in-out forwards;
}
.composer-ai-helper-modal__suggestion.thinking mark.highlight {
animation: mark-blink 1s step-start 0s infinite;
animation-name: mark-blink;
}
.composer-ai-helper-modal__loading {
white-space: pre-wrap;
}
.composer-ai-helper-modal__suggestion.inline-diff {
white-space: pre-wrap;
del:last-child {
text-decoration: none;
background-color: transparent;
color: var(--primary-low-mid);
}
.diff-inner {
display: inline;
}
}
@@ -78,6 +78,7 @@ body.has-ai-conversations-sidebar {
.private-message-glyph-wrapper,
.topic-header-participants,
.topic-above-footer-buttons-outlet,
.topic-map,
#topic-footer-buttons .topic-footer-main-buttons details {
display: none;
}
@@ -454,82 +455,4 @@ body.has-ai-conversations-sidebar {
display: none;
}
}
.topic-map {
box-sizing: border-box;
width: 100%;
margin: 0 auto;
padding-block: 0.5em;
.topic-map__views-trigger,
.topic-map__likes-trigger,
.summarization-button,
&__private-message-map,
.topic-map__users-list {
display: none;
}
&.--bottom {
padding-top: 0;
}
section {
background: transparent;
border-block: 1px solid var(--primary-low);
}
&__contents {
padding: 0.5em 1.25em;
@include viewport.from(sm) {
padding: 0.5em 0.5em 0.5em 1.9em;
}
}
&__stats {
height: 100%;
flex-wrap: nowrap;
gap: 1em;
}
.ai-conversation__participants {
display: flex;
flex-wrap: wrap;
gap: 0.5em 0.25em;
align-items: center;
.avatar {
width: 2em;
height: 2em;
}
.trigger-group-card {
display: flex;
align-items: center;
border: 1px solid var(--primary-low);
border-radius: var(--d-button-border-radius);
padding: 0.25em 0.5em;
font-size: var(--font-down-1);
span {
position: relative;
top: -1px;
}
.d-icon {
position: relative;
top: 1px;
}
a {
color: var(--primary-medium);
}
}
.btn {
font-size: var(--font-down-1);
margin-right: 0.5em;
}
}
}
}
@@ -1,9 +1,6 @@
.composer-ai-helper-modal {
.text-preview,
.inline-diff {
font-family: var(--d-font-family--monospace);
font-variant-ligatures: none;
ins {
background-color: var(--success-low);
text-decoration: none;
-13
View File
@@ -320,18 +320,9 @@ de:
rag_conversation_chunks_help: "Die Anzahl der Chunks, die für die RAG-Modell-Suche verwendet werden. Erhöhen, um die Menge des Kontexts zu steigern, den die KI verwenden kann."
persona_description: "Personas sind eine leistungsstarke Funktion, mit der du das Verhalten der KI-Engine in deinem Discourse-Forum anpassen kannst. Sie fungieren als „Systemnachricht“, welche die Antworten und Interaktionen der KI steuert und dazu beiträgt, ein persönlicheres und ansprechenderes Erlebnis für Benutzer zu schaffen."
response_format:
title: "JSON-Antwortformat"
no_format: "Kein JSON-Format angegeben"
open_modal: "Bearbeiten"
modal:
root_title: "Antwortstruktur"
key_title: "Schlüssel"
examples:
title: Beispiele
new: Neues Beispiel
remove: Beispiel löschen
collapsable_title: "Beispiel #%{number}"
model: "Antwort des Modells"
list:
enabled: "KI-Bot?"
ai_bot:
@@ -523,7 +514,6 @@ de:
disable_streaming: "Streaming-Vervollständigung deaktivieren (Streaming-Anfragen in Nicht-Streaming-Anfragen umwandeln)"
reasoning_effort: "Argumentationsaufwand (gilt nur für Argumentationsmodelle)"
enable_reasoning: "Argumentation aktivieren (gilt nur für Sonnet 3.7)"
thinking_tokens: "Anzahl der zum Denken verwendeten Token"
reasoning_tokens: "Anzahl der für die Argumentation verwendeten Token"
disable_temperature: "Temperatur deaktivieren (einige Denkmodelle unterstützen keine Temperatur)"
disable_top_p: "Top P deaktivieren (einige Denkmodelle unterstützen Top P nicht)"
@@ -711,9 +701,6 @@ de:
disclaimer: "Generative KI kann Fehler machen. Überprüfe wichtige Informationen."
placeholder: "Stell eine Frage ..."
new: "Neue Frage"
min_input_length_message:
one: "Nachricht muss mindestens 1 Zeichen lang sein"
other: "Nachricht muss mindestens %{count} Zeichen lang sein"
messages_sidebar_title: "Unterhaltungen"
today: "Heute"
last_7_days: "Letzte 7 Tage"
-4
View File
@@ -712,10 +712,6 @@ en:
name: "Share AI conversation"
title: "Share this AI conversation publicly"
invite_ai_conversation:
button: "Invite"
title: "Invite to AI conversation"
ai_label: "AI"
ai_title: "Conversation with AI"
-8
View File
@@ -309,13 +309,6 @@ he:
modal:
root_title: "מבנה תגובה"
key_title: "מפתח"
examples:
title: דוגמאות
new: דוגמה חדשה
remove: מחיקת דוגמה
collapsable_title: "דוגמה מס׳ %{number}"
user: "הודעת משתמש"
model: "תגובת המודל"
list:
enabled: "בוט בינה מלאכותית?"
ai_bot:
@@ -721,7 +714,6 @@ he:
discobot_discoveries:
regular_results: "נושאים"
tell_me_more: "אשמח לדעת עוד…"
continue_convo: "המשך בשיחה…"
collapse: "צמצום"
tooltip:
header: "חיפוש מופעל בינה מלאכותית"
+3 -3
View File
@@ -261,6 +261,9 @@ ar:
settings_explorer:
name: مستكشف الإعدادات
description: "روبوت ذكاء اصطناعي متخصص في المساعدة في استكشاف إعدادات موقع Discourse"
researcher:
name: الباحث
description: "روبوت ذكاء اصطناعي مزوَّد بإمكانية الوصول إلى Google، ويمكنه البحث عن المعلومات نيابةً عنك"
creative:
name: المبدع
description: "روبوت ذكاء اصطناعي من دون تكاملات خارجية متخصصة في المهام الإبداعية"
@@ -277,9 +280,6 @@ ar:
summarizing: "جارٍ تلخيص الموضوع"
searching: "جارٍ البحث عن: '%{query}'"
tool_options:
researcher:
max_results:
name: "الحد الأقصى لعدد النتائج"
google:
base_query:
name: "استعلام البحث الأساسي"
+2 -28
View File
@@ -106,7 +106,6 @@ de:
ai_discord_search_mode: "Wähle den Suchmodus für die Discord-Suche aus"
ai_discord_search_persona: "Die Persona, die für die Discord-Suche verwendet wird."
ai_discord_allowed_guilds: "Discord-Gilden (Server), auf denen der Bot suchen darf"
ai_bot_enable_dedicated_ux: "Erlaube eine Vollbild-Bot-Oberfläche anstelle einer PM"
reviewables:
reasons:
flagged_by_toxicity: Das KI-Plug-in meldete dies nach der Klassifizierung als toxisch.
@@ -279,11 +278,6 @@ de:
artist:
name: Künstler
description: "KI-Bot, spezialisiert auf die Erstellung von Bildern"
designer:
name: Designer
forum_researcher:
name: Forum-Rechercheur
description: "KI-Bot, spezialisiert auf gründliche Recherche für das Forum"
sql_helper:
name: SQL-Helfer
description: "KI-Bot, der darauf spezialisiert ist, bei der Erstellung von SQL-Abfragen auf dieser Discourse-Instanz zu helfen"
@@ -291,8 +285,8 @@ de:
name: Einstellungs-Explorer
description: "KI-Bot, der darauf spezialisiert ist, die Einstellungen der Discourse-Website zu erkunden"
researcher:
name: Web-Rechercheur
description: "KI-Bot mit Google-Zugriff, der Webseiten sowohl suchen als auch lesen kann"
name: Forscher
description: "KI-Bot mit Google-Zugang, der Informationen für dich recherchieren kann"
creative:
name: Kreativ
description: "KI-Bot ohne externe Integrationen, spezialisiert auf kreative Aufgaben"
@@ -315,16 +309,6 @@ de:
summarizing: "Thema zusammenfassen"
searching: "Suche nach: „%{query}“"
tool_options:
researcher:
max_results:
name: "Maximale Anzahl von Ergebnissen"
description: "Maximale Anzahl von Ergebnissen, die in einen Filter aufgenommen werden"
include_private:
name: "Private einschließen"
description: "Private Themen in die Filter einbeziehen"
max_tokens_per_post:
name: "Maximale Token pro Beitrag"
description: "Maximale Anzahl von Token, die für jeden Beitrag im Filter verwendet werden"
create_artifact:
creator_llm:
name: "LLM"
@@ -381,7 +365,6 @@ de:
dall_e: "Bild generieren"
search_meta_discourse: "Meta-Discourse durchsuchen"
javascript_evaluator: "JavaScript auswerten"
researcher: "Rechercheur"
tool_help:
read_artifact: "Lesen eines Webartefakts mit dem KI-Bot"
update_artifact: "Aktualisiere ein Web-Artefakt mit dem KI-Bot"
@@ -406,7 +389,6 @@ de:
dall_e: "Bild mit DALL-E 3 generieren"
search_meta_discourse: "Meta-Discourse durchsuchen"
javascript_evaluator: "JavaScript auswerten"
researcher: "Foreninformationen mit dem KI-Bot recherchieren"
tool_description:
read_artifact: "Lesen eines Webartefakts mit dem KI-Bot"
update_artifact: "Ein Web-Artefakt wurde mit dem KI-Bot aktualisiert"
@@ -441,12 +423,6 @@ de:
other: "%{count} <a href='%{url}'>Ergebnisse</a> für „%{query}“ gefunden"
setting_context: "Kontext wird gelesen für: %{setting_name}"
schema: "%{tables}"
researcher_dry_run:
one: "Vorgeschlagene Ziele: %{goals}\n\n%{count} Beitrag passend zu „%{filter}“ gefunden"
other: "Vorgeschlagene Ziele: %{goals}\n\n%{count} Beiträge passend zu „%{filter}“ gefunden"
researcher:
one: "Recherche: %{goals}\n\n%{count} Beitrag passend zu „%{filter}“ gefunden"
other: "Recherche: %{goals}\n\n%{count} Beiträge passend zu „%{filter}“ gefunden"
search_settings:
one: "%{count} Ergebnis für „%{query}“ gefunden"
other: "%{count} Ergebnisse für „%{query}“ gefunden"
@@ -492,8 +468,6 @@ de:
one: "Wir konnten dieses Modell nicht löschen, weil es von %{settings} verwendet wird. Aktualisiere die Einstellung und versuche es erneut."
other: "Wir konnten dieses Modell nicht löschen, weil %{settings} es verwenden. Aktualisiere die Einstellungen und versuche es erneut."
cannot_edit_builtin: "Du kannst ein integriertes Modell nicht bearbeiten."
personas:
malformed_examples: "Die angegebenen Beispiele haben das falsche Format."
embeddings:
delete_failed: "Dieses Modell wird derzeit verwendet. Aktualisiere zuerst `ai embeddings selected model`."
cannot_edit_builtin: "Du kannst ein integriertes Modell nicht bearbeiten."
+2 -25
View File
@@ -3,7 +3,6 @@ en:
ai:
flag_types:
review: "Add post to review queue"
review_hide: "Add post to review queue and hide post"
spam: "Flag as spam and hide post"
spam_silence: "Flag as spam, hide post and silence user"
scriptables:
@@ -297,9 +296,6 @@ en:
designer:
name: Designer
description: "AI Bot specialized in generating and editing images"
forum_researcher:
name: Forum Researcher
description: "AI Bot specialized in deep research for the forum"
sql_helper:
name: SQL Helper
description: "AI Bot specialized in helping craft SQL queries on this Discourse instance"
@@ -307,8 +303,8 @@ en:
name: Settings Explorer
description: "AI Bot specialized in helping explore Discourse site settings"
researcher:
name: Web Researcher
description: "AI Bot with Google access that can both search and read web pages"
name: Researcher
description: "AI Bot with Google access that can research information for you"
creative:
name: Creative
description: "AI Bot with no external integrations specialized in creative tasks"
@@ -331,16 +327,6 @@ en:
summarizing: "Summarizing topic"
searching: "Searching for: '%{query}'"
tool_options:
researcher:
max_results:
name: "Maximum number of results"
description: "Maximum number of results to include in a filter"
include_private:
name: "Include private"
description: "Include private topics in the filters"
max_tokens_per_post:
name: "Maximum tokens per post"
description: "Maximum number of tokens to use for each post in the filter"
create_artifact:
creator_llm:
name: "LLM"
@@ -399,8 +385,6 @@ en:
javascript_evaluator: "Evaluate JavaScript"
create_image: "Creating image"
edit_image: "Editing image"
researcher: "Researching"
researcher_dry_run: "Preparing research"
tool_help:
read_artifact: "Read a web artifact using the AI Bot"
update_artifact: "Update a web artifact using the AI Bot"
@@ -427,7 +411,6 @@ en:
dall_e: "Generate image using DALL-E 3"
search_meta_discourse: "Search Meta Discourse"
javascript_evaluator: "Evaluate JavaScript"
researcher: "Research forum information using the AI Bot"
tool_description:
read_artifact: "Read a web artifact using the AI Bot"
update_artifact: "Updated a web artifact using the AI Bot"
@@ -462,12 +445,6 @@ en:
other: "Found %{count} <a href='%{url}'>results</a> for '%{query}'"
setting_context: "Reading context for: %{setting_name}"
schema: "%{tables}"
researcher_dry_run:
one: "Proposed goals: %{goals}\n\nFound %{count} post matching '%{filter}'"
other: "Proposed goals: %{goals}\n\nFound %{count} posts matching '%{filter}'"
researcher:
one: "Researching: %{goals}\n\nFound %{count} post matching '%{filter}'"
other: "Researching: %{goals}\n\nFound %{count} posts matching '%{filter}'"
search_settings:
one: "Found %{count} result for '%{query}'"
other: "Found %{count} results for '%{query}'"

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