FEATURE: more elegant progress (#409)
Previous to this change it was very hard to tell if completion was stuck or not. This introduces a "dot" that follows the completion and starts flashing after 5 seconds.
This commit is contained in:
parent
b0a0cbe3ca
commit
05f7808057
|
@ -1,3 +1,4 @@
|
||||||
|
import { later } from "@ember/runloop";
|
||||||
import { hbs } from "ember-cli-htmlbars";
|
import { hbs } from "ember-cli-htmlbars";
|
||||||
import { ajax } from "discourse/lib/ajax";
|
import { ajax } from "discourse/lib/ajax";
|
||||||
import { popupAjaxError } from "discourse/lib/ajax-error";
|
import { popupAjaxError } from "discourse/lib/ajax-error";
|
||||||
|
@ -94,15 +95,41 @@ function initializeAIBotReplies(api) {
|
||||||
onAIBotStreamedReply: function (data) {
|
onAIBotStreamedReply: function (data) {
|
||||||
const post = this.model.postStream.findLoadedPost(data.post_id);
|
const post = this.model.postStream.findLoadedPost(data.post_id);
|
||||||
|
|
||||||
|
// it may take us a few seconds to load the post
|
||||||
|
// we need to requeue the event
|
||||||
|
if (!post && !data.done) {
|
||||||
|
const refresh = this.onAIBotStreamedReply.bind(this);
|
||||||
|
data.retries = data.retries || 5;
|
||||||
|
data.retries -= 1;
|
||||||
|
data.skipIfStreaming = true;
|
||||||
|
if (data.retries > 0) {
|
||||||
|
later(() => {
|
||||||
|
refresh(data);
|
||||||
|
}, 1000);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (post) {
|
if (post) {
|
||||||
if (data.raw) {
|
if (data.raw) {
|
||||||
|
const postElement = document.querySelector(
|
||||||
|
`#post_${data.post_number}`
|
||||||
|
);
|
||||||
|
|
||||||
|
if (
|
||||||
|
data.skipIfStreaming &&
|
||||||
|
postElement.classList.contains("streaming")
|
||||||
|
) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
cook(data.raw).then((cooked) => {
|
cook(data.raw).then((cooked) => {
|
||||||
post.set("raw", data.raw);
|
post.set("raw", data.raw);
|
||||||
post.set("cooked", cooked);
|
post.set("cooked", cooked);
|
||||||
|
|
||||||
document
|
// resets animation
|
||||||
.querySelector(`#post_${data.post_number}`)
|
postElement.classList.remove("streaming");
|
||||||
.classList.add("streaming");
|
void postElement.offsetWidth;
|
||||||
|
postElement.classList.add("streaming");
|
||||||
|
|
||||||
const cookedElement = document.createElement("div");
|
const cookedElement = document.createElement("div");
|
||||||
cookedElement.innerHTML = cooked;
|
cookedElement.innerHTML = cooked;
|
||||||
|
@ -131,9 +158,12 @@ function initializeAIBotReplies(api) {
|
||||||
this.model.details.allowed_users &&
|
this.model.details.allowed_users &&
|
||||||
this.model.details.allowed_users.filter(isGPTBot).length >= 1
|
this.model.details.allowed_users.filter(isGPTBot).length >= 1
|
||||||
) {
|
) {
|
||||||
|
// we attempt to recover the last message in the bus
|
||||||
|
// so we subscribe at -2
|
||||||
this.messageBus.subscribe(
|
this.messageBus.subscribe(
|
||||||
`discourse-ai/ai-bot/topic/${this.model.id}`,
|
`discourse-ai/ai-bot/topic/${this.model.id}`,
|
||||||
this.onAIBotStreamedReply.bind(this)
|
this.onAIBotStreamedReply.bind(this),
|
||||||
|
-2
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -47,6 +47,35 @@ article.streaming nav.post-controls .actions button.cancel-streaming {
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@keyframes flashing {
|
||||||
|
0%,
|
||||||
|
100% {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
article.streaming .cooked > {
|
||||||
|
:not(ol):not(ul):not(pre):last-child::after,
|
||||||
|
ol:last-child li:last-child p:last-child::after,
|
||||||
|
ol:last-child li:last-child:not(:has(p))::after,
|
||||||
|
ul:last-child li:last-child p:last-child::after,
|
||||||
|
ul:last-child li:last-child:not(:has(p))::after,
|
||||||
|
pre:last-child code::after {
|
||||||
|
content: "\25CF";
|
||||||
|
font-family: Söhne Circle, system-ui, -apple-system, Segoe UI, Roboto,
|
||||||
|
Ubuntu, Cantarell, Noto Sans, sans-serif;
|
||||||
|
line-height: normal;
|
||||||
|
margin-left: 0.25rem;
|
||||||
|
vertical-align: baseline;
|
||||||
|
|
||||||
|
animation: flashing 1.5s 3s infinite;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.ai-bot-available-bot-options {
|
.ai-bot-available-bot-options {
|
||||||
.ai-bot-available-bot-content {
|
.ai-bot-available-bot-content {
|
||||||
color: var(--primary-high);
|
color: var(--primary-high);
|
||||||
|
|
|
@ -135,8 +135,6 @@ en:
|
||||||
dall_e_3: "Image by DALL-E 3"
|
dall_e_3: "Image by DALL-E 3"
|
||||||
|
|
||||||
ai_bot:
|
ai_bot:
|
||||||
placeholder_reply: "I will reply shortly..."
|
|
||||||
|
|
||||||
personas:
|
personas:
|
||||||
cannot_delete_system_persona: "System personas cannot be deleted, please disable it instead"
|
cannot_delete_system_persona: "System personas cannot be deleted, please disable it instead"
|
||||||
cannot_edit_system_persona: "System personas can only be renamed, you may not edit commands or system prompt, instead disable and make a copy"
|
cannot_edit_system_persona: "System personas can only be renamed, you may not edit commands or system prompt, instead disable and make a copy"
|
||||||
|
|
|
@ -152,10 +152,17 @@ module DiscourseAi
|
||||||
<summary>#{summary}</summary>
|
<summary>#{summary}</summary>
|
||||||
<p>#{details}</p>
|
<p>#{details}</p>
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
HTML
|
HTML
|
||||||
|
|
||||||
placeholder << custom_raw if custom_raw
|
if custom_raw
|
||||||
|
placeholder << "\n"
|
||||||
|
placeholder << custom_raw
|
||||||
|
else
|
||||||
|
# we need this for cursor placeholder to work
|
||||||
|
# doing this in CSS is very hard
|
||||||
|
# if changing test with a custom tool such as search
|
||||||
|
placeholder << "<span></span>\n\n"
|
||||||
|
end
|
||||||
|
|
||||||
placeholder
|
placeholder
|
||||||
end
|
end
|
||||||
|
|
|
@ -107,10 +107,12 @@ module DiscourseAi
|
||||||
PostCreator.create!(
|
PostCreator.create!(
|
||||||
bot.bot_user,
|
bot.bot_user,
|
||||||
topic_id: post.topic_id,
|
topic_id: post.topic_id,
|
||||||
raw: I18n.t("discourse_ai.ai_bot.placeholder_reply"),
|
raw: "",
|
||||||
skip_validations: true,
|
skip_validations: true,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
publish_update(reply_post, raw: "<p></p>")
|
||||||
|
|
||||||
redis_stream_key = "gpt_cancel:#{reply_post.id}"
|
redis_stream_key = "gpt_cancel:#{reply_post.id}"
|
||||||
Discourse.redis.setex(redis_stream_key, 60, 1)
|
Discourse.redis.setex(redis_stream_key, 60, 1)
|
||||||
|
|
||||||
|
|
|
@ -36,7 +36,7 @@ module DiscourseAi
|
||||||
def invoke(bot_user, _llm)
|
def invoke(bot_user, _llm)
|
||||||
# max 4 prompts
|
# max 4 prompts
|
||||||
max_prompts = prompts.take(4)
|
max_prompts = prompts.take(4)
|
||||||
progress = +""
|
progress = prompts.first
|
||||||
|
|
||||||
yield(progress)
|
yield(progress)
|
||||||
|
|
||||||
|
@ -70,11 +70,7 @@ module DiscourseAi
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
while true
|
break if threads.all? { |t| t.join(2) } while true
|
||||||
progress << "."
|
|
||||||
yield(progress)
|
|
||||||
break if threads.all? { |t| t.join(2) }
|
|
||||||
end
|
|
||||||
|
|
||||||
results = threads.filter_map(&:value)
|
results = threads.filter_map(&:value)
|
||||||
|
|
||||||
|
|
|
@ -51,7 +51,7 @@ module DiscourseAi
|
||||||
selected_prompts = prompts.take(4)
|
selected_prompts = prompts.take(4)
|
||||||
seeds = seeds.take(4) if seeds
|
seeds = seeds.take(4) if seeds
|
||||||
|
|
||||||
progress = +""
|
progress = prompts.first
|
||||||
yield(progress)
|
yield(progress)
|
||||||
|
|
||||||
results = nil
|
results = nil
|
||||||
|
@ -85,11 +85,7 @@ module DiscourseAi
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
while true
|
break if threads.all? { |t| t.join(2) } while true
|
||||||
progress << "."
|
|
||||||
yield(progress)
|
|
||||||
break if threads.all? { |t| t.join(2) }
|
|
||||||
end
|
|
||||||
|
|
||||||
results = threads.map(&:value).compact
|
results = threads.map(&:value).compact
|
||||||
|
|
||||||
|
|
|
@ -45,6 +45,7 @@ RSpec.describe DiscourseAi::AiBot::Bot do
|
||||||
<summary>#{tool.summary}</summary>
|
<summary>#{tool.summary}</summary>
|
||||||
<p></p>
|
<p></p>
|
||||||
</details>
|
</details>
|
||||||
|
<span></span>
|
||||||
|
|
||||||
HTML
|
HTML
|
||||||
|
|
||||||
|
|
|
@ -67,7 +67,9 @@ RSpec.describe DiscourseAi::AiBot::Playground do
|
||||||
done_signal = messages.pop
|
done_signal = messages.pop
|
||||||
expect(done_signal.data[:done]).to eq(true)
|
expect(done_signal.data[:done]).to eq(true)
|
||||||
|
|
||||||
messages.each_with_index do |m, idx|
|
# we need this for styling
|
||||||
|
expect(messages.first.data[:raw]).to eq("<p></p>")
|
||||||
|
messages[1..-1].each_with_index do |m, idx|
|
||||||
expect(m.data[:raw]).to eq(expected_bot_response[0..idx])
|
expect(m.data[:raw]).to eq(expected_bot_response[0..idx])
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue