From e666266473fe20db26de6e11b70115f5e911a809 Mon Sep 17 00:00:00 2001 From: Keegan George Date: Wed, 18 Sep 2024 09:53:54 -0700 Subject: [PATCH] DEV: Make indicator wave a reusable component (#807) Previously we had some hardcoded markup with scss making a loading indicator wave. This code was being duplicated and used in both semantic search and summarization. We want to add the indicator wave to the AI helper diff modal as well and have the text flashing instead of the loading spinner. To ensure we do not repeat ourselves, in this PR we turn the summary indicator wave into a reusable template only component called: `AiIndicatorWave`. We then apply the usage of that component to semantic search, summarization, and the composer helper modal. --- .../components/ai-indicator-wave.gjs | 12 +++++++ .../components/ai-summary-skeleton.gjs | 7 ++-- .../discourse/components/modal/diff-modal.gjs | 36 +++++++++---------- .../semantic-search.gjs | 9 ++--- assets/stylesheets/common/streaming.scss | 29 +++++++++++++++ .../embeddings/common/semantic-search.scss | 20 +++-------- .../summarization/common/ai-summary.scss | 27 -------------- config/locales/client.en.yml | 2 +- .../components/ai-indicator-wave-test.gjs | 18 ++++++++++ 9 files changed, 86 insertions(+), 74 deletions(-) create mode 100644 assets/javascripts/discourse/components/ai-indicator-wave.gjs create mode 100644 test/javascripts/integration/components/ai-indicator-wave-test.gjs diff --git a/assets/javascripts/discourse/components/ai-indicator-wave.gjs b/assets/javascripts/discourse/components/ai-indicator-wave.gjs new file mode 100644 index 00000000..92ec6370 --- /dev/null +++ b/assets/javascripts/discourse/components/ai-indicator-wave.gjs @@ -0,0 +1,12 @@ +const indicatorDots = [".", ".", "."]; +const AiIndicatorWave = ; + +export default AiIndicatorWave; diff --git a/assets/javascripts/discourse/components/ai-summary-skeleton.gjs b/assets/javascripts/discourse/components/ai-summary-skeleton.gjs index a09daa6f..07c9e26b 100644 --- a/assets/javascripts/discourse/components/ai-summary-skeleton.gjs +++ b/assets/javascripts/discourse/components/ai-summary-skeleton.gjs @@ -9,6 +9,7 @@ import { cancel } from "@ember/runloop"; import concatClass from "discourse/helpers/concat-class"; import i18n from "discourse-common/helpers/i18n"; import discourseLater from "discourse-common/lib/later"; +import AiIndicatorWave from "./ai-indicator-wave"; class Block { @tracked show = false; @@ -118,11 +119,7 @@ export default class AiSummarySkeleton extends Component {
{{i18n "summary.in_progress"}}
- - . - . - . - + diff --git a/assets/javascripts/discourse/components/modal/diff-modal.gjs b/assets/javascripts/discourse/components/modal/diff-modal.gjs index aea982db..34be825f 100644 --- a/assets/javascripts/discourse/components/modal/diff-modal.gjs +++ b/assets/javascripts/discourse/components/modal/diff-modal.gjs @@ -3,13 +3,13 @@ import { tracked } from "@glimmer/tracking"; import { action } from "@ember/object"; import { inject as service } from "@ember/service"; import { htmlSafe } from "@ember/template"; -import ConditionalLoadingSpinner from "discourse/components/conditional-loading-spinner"; 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"; +import AiIndicatorWave from "../ai-indicator-wave"; export default class ModalDiffModal extends Component { @service currentUser; @@ -65,25 +65,23 @@ export default class ModalDiffModal extends Component { @closeModal={{@closeModal}} > <:body> - - {{#if this.loading}} -
- -
+ {{#if this.loading}} +
+ +
+ {{else}} + {{#if this.diff}} + {{htmlSafe this.diff}} {{else}} - {{#if this.diff}} - {{htmlSafe this.diff}} - {{else}} -
- {{@model.selectedText}} -
+
+ {{@model.selectedText}} +
-
- {{this.suggestion}} -
- {{/if}} +
+ {{this.suggestion}} +
{{/if}} -
+ {{/if}} @@ -93,7 +91,9 @@ export default class ModalDiffModal extends Component { class="btn-primary" @label="discourse_ai.ai_helper.context_menu.loading" @disabled={{true}} - /> + > + + {{else}} - {{#if this.searching}} - - . - . - . - - {{/if}} + diff --git a/assets/stylesheets/common/streaming.scss b/assets/stylesheets/common/streaming.scss index 9608749a..3c9c08d3 100644 --- a/assets/stylesheets/common/streaming.scss +++ b/assets/stylesheets/common/streaming.scss @@ -29,3 +29,32 @@ article.streaming .cooked { animation: flashing 1.5s infinite; } } + +@keyframes ai-indicator-wave { + 0%, + 60%, + 100% { + transform: initial; + } + 30% { + transform: translateY(-0.2em); + } +} + +.ai-indicator-wave { + flex: 0 0 auto; + display: inline-flex; + + &__dot { + display: inline-block; + @media (prefers-reduced-motion: no-preference) { + animation: ai-indicator-wave 1.8s linear infinite; + } + &:nth-child(2) { + animation-delay: -1.6s; + } + &:nth-child(3) { + animation-delay: -1.4s; + } + } +} diff --git a/assets/stylesheets/modules/embeddings/common/semantic-search.scss b/assets/stylesheets/modules/embeddings/common/semantic-search.scss index c41c9ca8..c6e67aa9 100644 --- a/assets/stylesheets/modules/embeddings/common/semantic-search.scss +++ b/assets/stylesheets/modules/embeddings/common/semantic-search.scss @@ -7,6 +7,10 @@ flex-direction: column; align-items: baseline; + .ai-indicator-wave { + color: var(--primary-medium); + } + .semantic-search { &__searching { display: flex; @@ -23,22 +27,6 @@ display: inline-block; margin-left: 3px; } - - &__indicator-wave { - flex: 0 0 auto; - display: inline-flex; - color: var(--primary-medium); - } - &__indicator-dot { - display: inline-block; - animation: ai-summary__indicator-wave 1.8s linear infinite; - &:nth-child(2) { - animation-delay: -1.6s; - } - &:nth-child(3) { - animation-delay: -1.4s; - } - } } .semantic-search__entries { diff --git a/assets/stylesheets/modules/summarization/common/ai-summary.scss b/assets/stylesheets/modules/summarization/common/ai-summary.scss index 2c469bf3..3bd13803 100644 --- a/assets/stylesheets/modules/summarization/common/ai-summary.scss +++ b/assets/stylesheets/modules/summarization/common/ai-summary.scss @@ -157,22 +157,6 @@ display: inline-block; margin-left: 3px; } - &__indicator-wave { - flex: 0 0 auto; - display: inline-flex; - } - &__indicator-dot { - display: inline-block; - @media (prefers-reduced-motion: no-preference) { - animation: ai-summary__indicator-wave 1.8s linear infinite; - } - &:nth-child(2) { - animation-delay: -1.6s; - } - &:nth-child(3) { - animation-delay: -1.4s; - } - } } .placeholder-summary { @@ -211,17 +195,6 @@ } } -@keyframes ai-summary__indicator-wave { - 0%, - 60%, - 100% { - transform: initial; - } - 30% { - transform: translateY(-0.2em); - } -} - @keyframes appear { 0% { opacity: 0; diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml index 3d7cad56..faae0e05 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -294,7 +294,7 @@ en: missing_content: "Please enter some content to generate suggestions." context_menu: trigger: "Ask AI" - loading: "AI is generating..." + loading: "AI is generating" cancel: "Cancel" regen: "Try Again" confirm: "Confirm" diff --git a/test/javascripts/integration/components/ai-indicator-wave-test.gjs b/test/javascripts/integration/components/ai-indicator-wave-test.gjs new file mode 100644 index 00000000..54038a25 --- /dev/null +++ b/test/javascripts/integration/components/ai-indicator-wave-test.gjs @@ -0,0 +1,18 @@ +import { render } from "@ember/test-helpers"; +import { module, test } from "qunit"; +import { setupRenderingTest } from "discourse/tests/helpers/component-test"; +import AiIndicatorWave from "discourse/plugins/discourse-ai/discourse/components/ai-indicator-wave"; + +module("Integration | Component | ai-indicator-wave", function (hooks) { + setupRenderingTest(hooks); + + test("it renders an indicator wave", async function (assert) { + await render(); + assert.dom(".ai-indicator-wave").exists(); + }); + + test("it does not render the indicator wave when loading is false", async function (assert) { + await render(); + assert.dom(".ai-indicator-wave").doesNotExist(); + }); +});