Compare commits

...
Author SHA1 Message Date
Jordan Vidrine 5b012a15b8 lint 2025-05-27 16:24:13 -05:00
Jordan Vidrine 051ac3ea49 more 2025-05-27 15:49:55 -05:00
Jordan Vidrine d993b02975 bring sidebar styles closer to core 2025-05-27 15:19:35 -05:00
Discourse Translator Bot 39653aed22 Update translations (#1371) 2025-05-27 22:11:32 +02:00
Sam 6781ecd02e DEV: cleanup diff streaming (#1370)
This simplifies some of the internal logic and ensures it is a
bit more robust
2025-05-27 18:12:02 +10:00
Keegan George e264572597 FIX: Closing AI menu stops post audio from playing (#1369)
## 🔍 Overview
When you have a post with audio being played and you open and close the AI post helper menu, it re-renders the entire post DOM, causing the audio to be interrupted and stop playing.

The reason for this is because we highlight the selected text when opening the AI post helper menu and we replace the entire post back with the original post HTML when closing the menu. This fix ensures that we do not re-render the entire post DOM and instead only remove the highlighted section that was added.

## 🔗 Context
https://meta.discourse.org/t/ai-helper-interrupting-uploaded-mp3-audio-stream/366817?u=keegan
2025-05-26 10:24:06 -07:00
Sam 70b0db2871 FIX: improve researcher tool - fix topic filters (#1368)
* Small fix, reasoning is now available on Claude 4 models

* fix invalid filters should raise, topic filter not working

* fix spec so we are consistent
2025-05-26 16:00:44 +10:00
Sam 2d6ec5e1e6 FIX: apply diffs more consistently (#1367)
* FIX: apply diffs more consistently

1. Do escaping direct in diff streamer, that way HTML tags and other unsafe chars can be displayed and fixed
2. Add safeguard to ensure streaming always stops when it was terminated elsewhere

* lint

* bug unsubscribe should unsubscribe
2025-05-24 15:19:48 +10:00
Mark VanLandingham cead887480 FIX: Don't error when navigating from AI Bot topic to regular (#1366)
We were getting an error in this logic causing Ember to fail to render the non-bot-topic that we navigate to.

I believe this is because the getter of participants is re-calculating (due to this.header.topicInfo being updated) before the args to this connector changes. Adding some safe navigation here fixes the issue.
2025-05-23 13:30:08 -05:00
Jordan Vidrine adc92e1e75 UX: Allow rounded corners (#1365) 2025-05-23 10:42:55 -05:00
Roman Rizzi 0ce17a122f FIX: Correctly pass tool_choice when using Claude models. (#1364)
The `ClaudePrompt` object couldn't access the original prompt's tool_choice attribute, affecting both Anthropic and Bedrock.
2025-05-23 10:36:52 -03:00
Sam cf220c530c FIX: Improve MessageBus efficiency and correctly stop streaming (#1362)
* FIX: Improve MessageBus efficiency and correctly stop streaming

This commit enhances the message bus implementation for AI helper streaming by:

- Adding client_id targeting for message bus publications to ensure only the requesting client receives streaming updates
- Limiting MessageBus backlog size (2) and age (60 seconds) to prevent Redis bloat
- Replacing clearTimeout with Ember's cancel method for proper runloop management, we were leaking a stop
- Adding tests for client-specific message delivery

These changes improve memory usage and make streaming more reliable by ensuring messages are properly directed to the requesting client.

* composer suggestion needed a fix as well.

* backlog size of 2 is risky here cause same channel name is reused between clients
2025-05-23 16:23:06 +10:00
Keegan George 61ef1932fa DEV: Follow-up small fixes to AI composer helper (#1361)
**This update includes a variety of small fixes to the AI composer helper:**
- ensures there is no flash of no text when diff modal is triggered
- escapes selected text prior to diff streaming
- uses monospace font in diff modal since text rendered is raw markdown
2025-05-22 12:37:42 -07:00
Keegan George 11e90f5f3f DEV: Hide proofreading shortcut in toolbar title (#1360) 2025-05-22 09:36:55 -07:00
Joffrey JAFFEUX b817810d46 FIX: ensures stream update object is scoped to its initial topic (#1359)
* FIX: ensures stream update object is scoped to its initial topic

Before this commit you could end up in this situation where a `post-updater` is constructed for a specific topic, but the user changes topic mid steam and it ends up updating the same post number but in a  different topic as we were only checking for `post_number` and not the combination of `topic_id` + `post_number`.

* we should have the topic
2025-05-22 20:08:43 +10:00
Sam 55dab9c68a FIX: stabilize diff algorithm for streaming (#1358)
Previous to this fix we would diff the entire body of text, this
could lead to situations where a diff presented to a user was
wildly off matching areas in the text that should not have been
tested.

New algorithm only checks a portion of the string, this ensures
that during streaming there is no chance for wild mistakes
2025-05-22 16:31:21 +10:00
Keegan GeorgeandSam Saffron c29183fc2d FEATURE: Add diff streaming animation (#1355)
Previously we attempted to add a diff streaming animation to the AI composer helper: https://github.com/discourse/discourse-ai/pull/1332, but it resulted in issues despite attempted fixes (https://github.com/discourse/discourse-ai/pull/1338) so we temporarily suppressed the diff animation (https://github.com/discourse/discourse-ai/pull/1341).

This update makes a second attempt at implementing the diff streaming animation. Instead of creating a custom diff algorithm, we make use of a third-party library [`jsDiff`](https://github.com/kpdecker/jsdiff) (which we added to core here: https://github.com/discourse/discourse/pull/32833). While streaming, the diff animation often struggles with markdown links and images, so we make use of `markdown-it` parser to detect those cases and prevent breaking the animation.

---------

Co-authored-by: Sam Saffron <[email protected]>
2025-05-22 08:10:50 +10:00
Roman Rizzi d72ad84f8f FIX: Retry parsing escaped inner JSON to handle control chars. (#1357)
The structured output JSON comes embedded inside the API response, which is also a JSON. Since we have to parse the response to process it, any control characters inside the structured output are unescaped into regular characters, leading to invalid JSON and breaking during parsing. This change adds a retry mechanism that escapes
the string again if parsing fails, preventing the parser from breaking on malformed input and working around this issue.

For example:

```
  original = '{ "a": "{\\"key\\":\\"value with \\n newline\\"}" }'
  JSON.parse(original) => { "a" => "{\"key\":\"value with \n newline\"}" }
  # At this point, the inner JSON string contains an actual newline.
```
2025-05-21 11:25:59 -03:00
Roman Rizzi e207eba1a4 FIX: Don't dig on nil when checking for the gemini schema (#1356) 2025-05-21 08:30:47 -03:00
Kris 53905f65ac FEATURE: add participants and invite button to AI conversations (#1354) 2025-05-21 10:27:06 +10:00
Guhyoun Nam ef260be7cf DEV: Move the stop-forcing-conversations-sidebar appEvents trigger (#1353) 2025-05-20 15:06:54 -05:00
Discourse Translator Bot 0c69bb85ce Update translations (#1352) 2025-05-20 17:33:52 +02:00
Sam af18603b21 DEV: cancel manager should bypass webmock (#1350)
Webmock can be a bit flaky under certain use cases.
2025-05-20 13:01:55 +10:00
Sam 7db2589cc4 DEV: prompt engineering to improve citations (#1351) 2025-05-20 13:01:35 +10:00
Roman Rizzi 2fb691cba8 FEATURE: Triage can hide posts after adding them to the review queue (#1348) 2025-05-20 08:19:00 +10:00
Sam de0625571b FIX: add safe navigation to serializer include conditions (#1349)
In some rare cases a post can exist without a topic
2025-05-19 18:55:55 -03:00
Joffrey JAFFEUX 296aa24df1 DEV: rewrites artifact spec with capybara waiters (#1347)
Generally speaking we never want to do:

```
expect(element.text).to eq("foo")
```

As these are rspec matchers and do not add further Capybara-style waiting specifically for the text content to become present.
2025-05-20 07:27:15 +10:00
99 changed files with 1280 additions and 300 deletions
@@ -124,6 +124,10 @@ 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,
@@ -132,6 +136,7 @@ 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!
@@ -146,6 +151,7 @@ module DiscourseAi
text: text,
prompt: prompt.name,
custom_prompt: params[:custom_prompt],
client_id: params[:client_id],
)
end
@@ -8,6 +8,7 @@ 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])
@@ -21,6 +22,7 @@ module Jobs
user,
"/discourse-ai/ai-helper/stream_composer_suggestion",
force_default_locale: args[:force_default_locale],
client_id: args[:client_id],
)
end
end
@@ -242,6 +242,7 @@ 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,18 +12,21 @@ 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";
import AiIndicatorWave from "../ai-indicator-wave";
const CHANNEL = "/discourse-ai/ai-helper/stream_composer_suggestion";
export default class ModalDiffModal extends Component {
@service currentUser;
@service messageBus;
@tracked loading = false;
@tracked finalResult = "";
@tracked showcasedDiff = "";
@tracked escapedSelectedText = escapeExpression(this.args.model.selectedText);
@tracked diffStreamer = new DiffStreamer(this.args.model.selectedText);
@tracked suggestion = "";
@tracked
@@ -31,13 +34,28 @@ export default class ModalDiffModal extends Component {
() => this.suggestion,
(newValue) => (this.suggestion = newValue)
);
@tracked isStreaming = false;
constructor() {
super(...arguments);
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.escapedSelectedText;
}
get isStreaming() {
// diffStreamer stops Streaming when it is finished with a chunk, looking at isDone is safe
// it starts off not done
return !this.diffStreamer.isDone || this.smoothStreamer.isStreaming;
}
get primaryBtnLabel() {
return this.loading
? i18n("discourse_ai.ai_helper.context_menu.loading")
@@ -50,35 +68,24 @@ export default class ModalDiffModal extends Component {
@bind
subscribe() {
const channel = "/discourse-ai/ai-helper/stream_composer_suggestion";
this.messageBus.subscribe(channel, this.updateResult);
this.messageBus.subscribe(CHANNEL, this.updateResult);
}
@bind
unsubscribe() {
const channel = "/discourse-ai/ai-helper/stream_composer_suggestion";
this.messageBus.subscribe(channel, this.updateResult);
cleanup() {
// stop all callbacks so it does not end up streaming pointlessly
this.smoothStreamer.resetStreaming();
this.diffStreamer.reset();
this.messageBus.unsubscribe(CHANNEL, this.updateResult);
}
@action
async updateResult(result) {
// TODO(@keegan)
// Temporarily we are removing the animation using the diff streamer
// and simply showing the diff streamed without a proper animation
// while we figure things out
// so that things are not too janky in the meantime.
updateResult(result) {
this.loading = false;
this.isStreaming = true;
if (result.done) {
this.finalResult = result.result;
}
this.showcasedDiff = result.diff;
if (result.done) {
this.loading = false;
this.isStreaming = false;
}
if (this.args.model.showResultAsDiff) {
@@ -103,6 +110,7 @@ export default class ModalDiffModal extends Component {
text: this.args.model.selectedText,
custom_prompt: this.args.model.customPromptValue,
force_default_locale: true,
client_id: this.messageBus.clientId,
},
});
} catch (e) {
@@ -125,6 +133,7 @@ export default class ModalDiffModal extends Component {
this.finalResult?.length > 0
? this.finalResult
: this.diffStreamer.suggestion;
if (this.args.model.showResultAsDiff && finalResult) {
this.args.model.toolbarEvent.replaceText(
this.args.model.selectedText,
@@ -140,10 +149,14 @@ export default class ModalDiffModal extends Component {
@closeModal={{@closeModal}}
>
<:body>
<div {{didInsert this.subscribe}} {{willDestroy this.unsubscribe}}>
<div
{{didInsert this.subscribe}}
{{willDestroy this.cleanup}}
class="text-preview"
>
{{#if this.loading}}
<div class="composer-ai-helper-modal__loading">
<CookText @rawText={{@model.selectedText}} />
{{~@model.selectedText~}}
</div>
{{else}}
<div
@@ -152,10 +165,11 @@ 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}}
{{htmlSafe this.showcasedDiff}}
{{~#if @model.showResultAsDiff~}}
<span class="diff-inner">{{htmlSafe this.diffResult}}</span>
{{else}}
{{#if this.smoothStreamer.isStreaming}}
<CookText
@@ -19,7 +19,6 @@ export default class AiPostHelperTrigger extends Component {
@tracked menuState = this.MENU_STATES.triggers;
@tracked showMainButtons = true;
@tracked showAiButtons = true;
@tracked originalPostHTML = null;
@tracked postHighlighted = false;
@tracked currentMenu = this.menu.getByIdentifier(
"post-text-selection-toolbar"
@@ -45,7 +44,6 @@ export default class AiPostHelperTrigger extends Component {
return;
}
this.originalPostHTML = postElement.innerHTML;
this.selectedText = this.args.outletArgs.data.quoteState.buffer;
const selection = window.getSelection();
@@ -121,7 +119,15 @@ export default class AiPostHelperTrigger extends Component {
return;
}
postElement.innerHTML = this.originalPostHTML;
const highlightedSpans = postElement.querySelectorAll(
"span.ai-helper-highlighted-selection"
);
highlightedSpans.forEach((span) => {
const textNode = document.createTextNode(span.textContent);
span.parentNode.replaceChild(textNode, span);
});
this.postHighlighted = false;
}
@@ -7,6 +7,10 @@ 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, {
@@ -18,15 +22,17 @@ export default class AiSummaryTrigger extends Component {
}
<template>
{{#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 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}}
</template>
}
@@ -0,0 +1,55 @@
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>
}
@@ -12,13 +12,15 @@ 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(
`#post_${this.post.post_number}`
`.topic-area[data-topic-id="${topicId}"] #post_${this.post.post_number}`
);
}
}
@@ -57,6 +59,10 @@ 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
@@ -42,8 +42,7 @@ 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");
}
this.appEvents.trigger("discourse-ai:stop-forcing-conversations-sidebar");
}
}
@@ -27,6 +27,7 @@ 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) {
+36 -2
View File
@@ -83,14 +83,48 @@ article.streaming .cooked {
@keyframes mark-blink {
0%,
100% {
border-color: var(--highlight-high);
border-color: transparent;
}
50% {
border-color: transparent;
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;
}
}
@@ -39,25 +39,22 @@ body.has-ai-conversations-sidebar {
.sidebar-section-link.date-heading {
pointer-events: none;
cursor: default;
color: var(--primary-medium);
opacity: 0.8;
font-weight: 700;
color: var(--d-sidebar-header-color);
margin-top: 1em;
font-size: var(--font-down-2);
font-weight: bold;
font-size: var(--font-down-2-rem);
text-transform: uppercase;
letter-spacing: 0.05rem;
}
.sidebar-section-link {
height: unset;
padding-block: 0.65em;
font-size: var(--font-down-1);
letter-spacing: 0.35px;
border-radius: 0 var(--border-radius) var(--border-radius) 0;
.sidebar-section-link-content-text {
white-space: normal;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 2;
-webkit-line-clamp: 1;
overflow: hidden;
}
}
@@ -78,7 +75,6 @@ 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;
}
@@ -455,4 +451,82 @@ 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,6 +1,11 @@
@use "lib/viewport";
.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;
@@ -176,7 +181,7 @@
@keyframes dot-falling {
0% {
box-shadow: 9999px -15px 0 0 rgba(152, 128, 255, 0);
box-shadow: 9999px -15px 0 0 rgb(152, 128, 255, 0);
}
25%,
@@ -186,13 +191,13 @@
}
100% {
box-shadow: 9999px 15px 0 0 rgba(152, 128, 255, 0);
box-shadow: 9999px 15px 0 0 rgb(152, 128, 255, 0);
}
}
@keyframes dot-falling-before {
0% {
box-shadow: 9984px -15px 0 0 rgba(152, 128, 255, 0);
box-shadow: 9984px -15px 0 0 rgb(152, 128, 255, 0);
}
25%,
@@ -202,13 +207,13 @@
}
100% {
box-shadow: 9984px 15px 0 0 rgba(152, 128, 255, 0);
box-shadow: 9984px 15px 0 0 rgb(152, 128, 255, 0);
}
}
@keyframes dot-falling-after {
0% {
box-shadow: 10014px -15px 0 0 rgba(152, 128, 255, 0);
box-shadow: 10014px -15px 0 0 rgb(152, 128, 255, 0);
}
25%,
@@ -218,7 +223,7 @@
}
100% {
box-shadow: 10014px 15px 0 0 rgba(152, 128, 255, 0);
box-shadow: 10014px 15px 0 0 rgb(152, 128, 255, 0);
}
}
@@ -229,19 +234,6 @@
input:focus {
z-index: 1;
}
input {
border-top-right-radius: 0;
border-bottom-right-radius: 0;
}
}
.category-chooser,
.mini-tag-chooser {
.select-kit-header {
border-top-right-radius: 0;
border-bottom-right-radius: 0;
}
}
#edit-title {
@@ -636,7 +628,7 @@
height: 7em;
min-width: var(--ai-caption-popup-min-width);
@include breakpoint(tablet) {
@include viewport.until(md) {
width: 100%;
max-width: unset;
min-width: unset;
+2
View File
@@ -603,6 +603,8 @@ ar:
share_ai_conversation:
name: "مشاركة محادثة الذكاء الاصطناعي"
title: "مشاركة هذه المحادثة بشكلٍ علني"
invite_ai_conversation:
button: "دعوة"
ai_label: "الذكاء الاصطناعي"
ai_title: "المحادثة مع الذكاء الاصطناعي"
share_modal:
+2
View File
@@ -134,6 +134,8 @@ bg:
request: "Заявка"
next_log: "Напред"
previous_log: "Предишни"
invite_ai_conversation:
button: "Покана"
share_modal:
copy: "Копирай"
conversations:
+2
View File
@@ -128,6 +128,8 @@ bs_BA:
response: "Odgovor"
next_log: "Iduće"
previous_log: "Previous"
invite_ai_conversation:
button: "Invite"
share_modal:
copy: "Copy"
conversations:
+2
View File
@@ -133,6 +133,8 @@ ca:
response: "Reacció"
next_log: "Següent"
previous_log: "Previ"
invite_ai_conversation:
button: "Convida"
share_modal:
copy: "Còpia"
conversations:
+45
View File
@@ -211,24 +211,69 @@ cs:
ai_artifact:
collapse_view_label: "Ukončení celé obrazovky (ESC nebo tlačítko Zpět)"
ai_bot:
pm_warning: "Zprávy chatbotů s umělou inteligencí jsou pravidelně monitorovány moderátory."
cancel_streaming: "Zastavit odpověď"
default_pm_prefix: "[Nepojmenovaná SZ od AI bota]"
shortcut_title: "Začněte SZ s AI botem"
share: "Kopírovat konverzaci s AI"
conversation_shared: "Konverzace zkopírována"
debug_ai: "Zobrazit nezpracovaný požadavek a odpověď umělé inteligence"
sidebar_empty: "Zde se zobrazí historie konverzace s botem."
debug_ai_modal:
title: "Zobrazit interakci s umělou inteligencí"
copy_request: "Kopírovat požadavek"
copy_response: "Kopírovat odpověď"
request_tokens: "Tokeny požadavku:"
response_tokens: "Tokeny odpovědi:"
request: "Požadavek"
response: "Odpověď"
next_log: "Další"
previous_log: "Předchozí"
share_full_topic_modal:
title: "Veřejně sdílet konverzaci"
share: "Sdílet a kopírovat odkaz"
update: "Aktualizovat a zkopírovat odkaz"
delete: "Odstranit sdílení"
share_ai_conversation:
name: "Sdílet konverzaci s AI"
title: "Sdílejte veřejně tuto konverzaci s umělou inteligencí"
invite_ai_conversation:
button: "Pozvat"
title: "Pozvat do konverzace s AI"
ai_title: "Konverzace s AI"
share_modal:
title: "Kopírovat konverzaci s AI"
copy: "Kopírovat"
context: "Interakce ke sdílení:"
share_tip: "Můžete také sdílet celou konverzaci."
bot_names:
fake: "Falešný testovací bot"
claude-3-opus: "Claude 3 Opus"
claude-3-sonnet: "Claude 3 Sonet"
claude-3-haiku: "Haiku Claude 3"
cohere-command-r-plus: "Cohere Command R Plus"
gpt-4: "GPT-4"
gpt-4-turbo: "GPT-4 Turbo"
gpt-4o: "GPT-4 Omni"
gpt-3:
5-turbo: "GPT-3.5"
claude-2: "Claude 2"
gemini-1:
5-pro: "Gemini"
mixtral-8x7B-Instruct-V0:
"1": "Mixtral-8x7B V0.1"
conversations:
header: "S čím mohu pomoci?"
submit: "Odeslat otázku"
disclaimer: "Generativní umělá inteligence může dělat chyby. Ověřte si důležité informace."
placeholder: "Zeptejte se..."
new: "Nová otázka"
min_input_length_message:
one: "Zpráva musí mít 1 nebo více znaků"
few: "Zpráva musí mít %{count} nebo více znaků"
many: "Zpráva musí mít %{count} nebo více znaků"
other: "Zpráva musí mít %{count} nebo více znaků"
messages_sidebar_title: "Konverzace"
today: "Dnes"
last_7_days: "Posledních 7 dní"
last_30_days: "Posledních 30 dní"
+2
View File
@@ -147,6 +147,8 @@ da:
response: "Svar"
next_log: "Næste"
previous_log: "Forrige"
invite_ai_conversation:
button: "Invitér"
share_modal:
copy: "Kopier"
conversations:
+21 -1
View File
@@ -320,9 +320,20 @@ 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
examples_help: Simuliere frühere Interaktionen mit dem LLM und erde es, um bessere Ergebnisse zu erzielen.
new: Neues Beispiel
remove: Beispiel löschen
collapsable_title: "Beispiel #%{number}"
user: "Nachricht des Benutzers"
model: "Antwort des Modells"
list:
enabled: "KI-Bot?"
ai_bot:
@@ -513,7 +524,9 @@ de:
provider_quantizations: "Reihenfolge der Provider-Quantisierungen (kommagetrennte Liste, z. B.: fp16,fp8)"
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)"
enable_reasoning: "Argumentation aktivieren (nur für Argumentationsmodelle anwendbar)"
enable_thinking: "Denken aktivieren (nur bei entsprechenden Modellen, z.B.: Flash 2.5)"
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)"
@@ -654,6 +667,7 @@ de:
share: "KI-Unterhaltung kopieren"
conversation_shared: "Unterhaltung kopiert"
debug_ai: "Rohdaten der KI-Anfrage und -Antwort anzeigen"
sidebar_empty: "Der Verlauf der Bot-Konversation wird hier angezeigt."
debug_ai_modal:
title: "KI-Interaktion ansehen"
copy_request: "Anfrage kopieren"
@@ -672,6 +686,9 @@ de:
share_ai_conversation:
name: "KI-Unterhaltung teilen"
title: "Diese KI-Unterhaltung öffentlich teilen"
invite_ai_conversation:
button: "Einladen"
title: "Zur KI-Konversation einladen"
ai_label: "KI"
ai_title: "Unterhaltung mit KI"
share_modal:
@@ -701,6 +718,9 @@ 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"
+2
View File
@@ -142,6 +142,8 @@ el:
response: "Απάντηση"
next_log: "Επόμενο"
previous_log: "Προηγούμενο"
invite_ai_conversation:
button: "Πρόσκληση"
share_modal:
copy: "Αντιγραφή"
conversations:
+5 -1
View File
@@ -540,7 +540,7 @@ en:
provider_quantizations: "Order of provider quantizations (comma delimited list eg: fp16,fp8)"
disable_streaming: "Disable streaming completions (convert streaming to non streaming requests)"
reasoning_effort: "Reasoning effort (only applicable to reasoning models)"
enable_reasoning: "Enable reasoning (only applicable to Sonnet 3.7)"
enable_reasoning: "Enable reasoning (only applicable to reasoning models)"
enable_thinking: "Enable thinking (only on applicable models eg: flash 2.5)"
thinking_tokens: "Number of tokens used for thinking"
reasoning_tokens: "Number of tokens used for reasoning"
@@ -712,6 +712,10 @@ 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"
+2
View File
@@ -591,6 +591,8 @@ es:
share_ai_conversation:
name: "Compartir conversación de IA"
title: "Compartir esta conversación de IA públicamente"
invite_ai_conversation:
button: "Invitar"
ai_label: "IA"
ai_title: "Conversación con IA"
share_modal:
+2
View File
@@ -130,6 +130,8 @@ et:
response: "Vastus"
next_log: "Järgmine"
previous_log: "Eelmine"
invite_ai_conversation:
button: "Kutsu"
share_modal:
copy: "Kopeeri"
conversations:
+2
View File
@@ -187,6 +187,8 @@ fa_IR:
response: "پاسخ"
next_log: "بعدی"
previous_log: "پیشین"
invite_ai_conversation:
button: "دعوت"
ai_label: "هوش مصنوعی"
share_modal:
copy: "کپی"
+2
View File
@@ -591,6 +591,8 @@ fi:
share_ai_conversation:
name: "Jaa tekoälykeskustelu"
title: "Jaa tämä tekoälykeskustelu julkisesti"
invite_ai_conversation:
button: "Kutsu"
ai_label: "Tekoäly"
ai_title: "Keskustelu tekoälyn kanssa"
share_modal:
+2
View File
@@ -591,6 +591,8 @@ fr:
share_ai_conversation:
name: "Partager une conversation IA"
title: "Partager publiquement cette conversation avec l'IA"
invite_ai_conversation:
button: "Inviter"
ai_label: "IA"
ai_title: "Conversation avec l'IA"
share_modal:
+2
View File
@@ -140,6 +140,8 @@ gl:
response: "Resposta"
next_log: "Seguinte"
previous_log: "Anterior"
invite_ai_conversation:
button: "Convidar"
share_modal:
copy: "Copiar"
conversations:
+15 -1
View File
@@ -309,6 +309,13 @@ he:
modal:
root_title: "מבנה תגובה"
key_title: "מפתח"
examples:
title: דוגמאות
new: דוגמה חדשה
remove: מחיקת דוגמה
collapsable_title: "דוגמה מס׳ %{number}"
user: "הודעת משתמש"
model: "תגובת המודל"
list:
enabled: "בוט בינה מלאכותית?"
ai_bot:
@@ -490,7 +497,8 @@ he:
disable_native_tools: "השבתת תמיכה בבלי מובנה (להשתמש בכלים מבוססי XML)"
provider_order: "סדר ספקים (רשימה מופרדת בפסיקים)"
provider_quantizations: "סדר כימות הספקים (רשימה מופרדת בפסיקים, למשל: fp16,fp8)"
enable_reasoning: "הפעלת נימוק (תקף רק על Sonnet 3.7)"
reasoning_effort: "מאמץ נימוק (תקף רק על מודלים של נימוק)"
enable_thinking: "הפעלת חשיבה (רק במודלים תקפים לדוגמה: flash 2.5)"
reasoning_tokens: "כמות אסימונים המשמשים לנימוק"
related_topics:
title: "נושאים קשורים"
@@ -624,6 +632,7 @@ he:
share: "העתקת דיון עם בינה מלאכותית"
conversation_shared: "הדיון הועתק"
debug_ai: "הצגת בקשה ותגובה גולמיות לבינה המלאכותית"
sidebar_empty: "היסטוריית השיחה עם הבוט תופיע כאן."
debug_ai_modal:
title: "הצגת התכתובת מול הבינה המלאכותית"
copy_request: "העתקת הבקשה"
@@ -642,6 +651,9 @@ he:
share_ai_conversation:
name: "שיתוף דיון עם בינה מלאכותית"
title: "שיתוף שיחת בינה מלאכותית באופן ציבורי"
invite_ai_conversation:
button: "להזמין"
title: "הזמנה לשיחה עם בינה מלאכותית"
ai_label: "בינה מלאכותית"
ai_title: "דיון עם בינה מלאכותית"
share_modal:
@@ -668,6 +680,7 @@ he:
conversations:
header: "איך אפשר לעזור?"
submit: "הגשת שאלה"
disclaimer: "בינה מלאכותית מחוללת עלולה לשגות. כדאי לאמת פרטים חשובים."
placeholder: "הגשת שאלה…"
new: "שאלה חדשה"
messages_sidebar_title: "שיחות"
@@ -714,6 +727,7 @@ he:
discobot_discoveries:
regular_results: "נושאים"
tell_me_more: "אשמח לדעת עוד…"
continue_convo: "המשך בשיחה…"
collapse: "צמצום"
tooltip:
header: "חיפוש מופעל בינה מלאכותית"

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