From fa07d887578b5f697743d89b4c2fa1d2f7ed1af3 Mon Sep 17 00:00:00 2001 From: Mark VanLandingham Date: Mon, 16 Dec 2024 10:59:19 -0600 Subject: [PATCH 1/4] FEATURE: Unavailable state for semantic search when sort is not Relevant --- .../semantic-search.gjs | 141 +++++++++++------- .../embeddings/common/semantic-search.scss | 6 +- config/locales/client.en.yml | 2 + plugin.rb | 2 + 4 files changed, 98 insertions(+), 53 deletions(-) diff --git a/assets/javascripts/discourse/connectors/full-page-search-below-search-header/semantic-search.gjs b/assets/javascripts/discourse/connectors/full-page-search-below-search-header/semantic-search.gjs index bb1da3f88..80d4bd84c 100644 --- a/assets/javascripts/discourse/connectors/full-page-search-below-search-header/semantic-search.gjs +++ b/assets/javascripts/discourse/connectors/full-page-search-below-search-header/semantic-search.gjs @@ -2,15 +2,16 @@ import Component from "@glimmer/component"; import { tracked } from "@glimmer/tracking"; import { on } from "@ember/modifier"; import { action } from "@ember/object"; -import didInsert from "@ember/render-modifiers/modifiers/did-insert"; +import didUpdate from "@ember/render-modifiers/modifiers/did-update"; import { service } from "@ember/service"; +import { not } from "truth-helpers"; import DToggleSwitch from "discourse/components/d-toggle-switch"; import { SEARCH_TYPE_DEFAULT } from "discourse/controllers/full-page-search"; import { ajax } from "discourse/lib/ajax"; -import { withPluginApi } from "discourse/lib/plugin-api"; import { isValidSearchTerm, translateResults } from "discourse/lib/search"; import icon from "discourse-common/helpers/d-icon"; -import I18n from "I18n"; +import I18n, { i18n } from "discourse-i18n"; +import DTooltip from "float-kit/components/d-tooltip"; import AiIndicatorWave from "../../components/ai-indicator-wave"; export default class SemanticSearch extends Component { @@ -18,27 +19,57 @@ export default class SemanticSearch extends Component { return siteSettings.ai_embeddings_semantic_search_enabled; } - @service router; @service appEvents; + @service router; @service siteSettings; @service searchPreferencesManager; - @tracked searching = false; + @tracked searching; @tracked AiResults = []; @tracked showingAiResults = false; + @tracked sortOrder = this.args.outletArgs.sortOrder; initialSearchTerm = this.args.outletArgs.search; + constructor() { + super(...arguments); + this.appEvents.on("full-page-search:trigger-search", this, this.onSearch); + this.handleSearch(); + } + + willDestroy() { + super.willDestroy(...arguments); + this.appEvents.off("full-page-search:trigger-search", this, this.onSearch); + } + + @action + onSearch() { + if (!this.searching) { + this.resetAiResults(); + return this.performHyDESearch(); + } + } + get disableToggleSwitch() { if ( this.searching || this.AiResults.length === 0 || - this.args.outletArgs.sortOrder !== 0 + !this.validSearchOrder ) { return true; } } + get validSearchOrder() { + return this.sortOrder === 0; + } + get searchStateText() { + if (!this.validSearchOrder) { + return I18n.t( + "discourse_ai.embeddings.semantic_search_results.unavailable" + ); + } + // Search results: if (this.AiResults.length > 0) { if (this.showingAiResults) { @@ -89,7 +120,7 @@ export default class SemanticSearch extends Component { return ( this.args.outletArgs.type === SEARCH_TYPE_DEFAULT && isValidSearchTerm(this.searchTerm, this.siteSettings) && - this.args.outletArgs.sortOrder === 0 + this.validSearchOrder ); } @@ -116,15 +147,13 @@ export default class SemanticSearch extends Component { return; } - if (this.initialSearchTerm && !this.searching) { + if (this.initialSearchTerm) { + this.searching = true; return this.performHyDESearch(); } - - this.#resetAndSearchOnEvent(); } performHyDESearch() { - this.searching = true; this.resetAiResults(); ajax("/discourse-ai/embeddings/semantic-search", { @@ -134,7 +163,6 @@ export default class SemanticSearch extends Component { const model = (await translateResults(results)) || {}; if (model.posts?.length === 0) { - this.searching = false; return; } @@ -144,56 +172,65 @@ export default class SemanticSearch extends Component { this.AiResults = model.posts; }) - .finally(() => (this.searching = false)); - } - - #resetAndSearchOnEvent() { - return withPluginApi("1.15.0", (api) => { - api.onAppEvent("full-page-search:trigger-search", () => { - if (!this.searching) { - this.resetAiResults(); - return this.performHyDESearch(); - } + .finally(() => { + this.searching = false; }); - }); } @action - checkQueryParamsAndSearch() { - // This check is necessary because handleSearch() isn't called - // if query params are present and a new search has appended text. - // It ensures AiResults are reset and searched for properly - const searchQueryParam = this.router.currentRoute?.queryParams?.q; - if (searchQueryParam) { - this.#resetAndSearchOnEvent(); + sortChanged() { + if (this.sortOrder !== this.args.outletArgs.sortOrder) { + this.sortOrder = this.args.outletArgs.sortOrder; + + if (this.validSearchOrder) { + this.handleSearch(); + } else { + this.showingAiResults = false; + this.resetAiResults(); + } } } } diff --git a/assets/stylesheets/modules/embeddings/common/semantic-search.scss b/assets/stylesheets/modules/embeddings/common/semantic-search.scss index c6e67aa9f..bf820d139 100644 --- a/assets/stylesheets/modules/embeddings/common/semantic-search.scss +++ b/assets/stylesheets/modules/embeddings/common/semantic-search.scss @@ -25,7 +25,11 @@ &__searching-text { display: inline-block; - margin-left: 3px; + margin-left: 8px; + } + + &__unavailable-tooltip { + margin-left: 4px; } } diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml index f34d1d171..39f559360 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -483,6 +483,8 @@ en: toggle_hidden: "Hiding %{count} results found using AI" none: "Sorry, our AI search found no matching topics" new: "Press 'search' to begin looking for new results with AI" + unavailable: "AI results unavailable" + semantic_search_unavailable_tooltip: "Search results must be sorted by Relevance to display AI results." ai_generated_result: "Search result found using AI" quick_search: suffix: "in all topics and posts with AI" diff --git a/plugin.rb b/plugin.rb index aac3eee31..bf8d20c03 100644 --- a/plugin.rb +++ b/plugin.rb @@ -43,6 +43,8 @@ register_asset "stylesheets/modules/ai-bot/common/ai-artifact.scss" +register_svg_icon "far-circle-question" if respond_to?(:register_svg_icon) + module ::DiscourseAi PLUGIN_NAME = "discourse-ai" From a975159feb227c6e3f673c1dacadcd80bd525410 Mon Sep 17 00:00:00 2001 From: Mark VanLandingham Date: Mon, 16 Dec 2024 11:06:47 -0600 Subject: [PATCH 2/4] lint --- .../full-page-search-below-search-header/semantic-search.gjs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/assets/javascripts/discourse/connectors/full-page-search-below-search-header/semantic-search.gjs b/assets/javascripts/discourse/connectors/full-page-search-below-search-header/semantic-search.gjs index 80d4bd84c..abc989fa0 100644 --- a/assets/javascripts/discourse/connectors/full-page-search-below-search-header/semantic-search.gjs +++ b/assets/javascripts/discourse/connectors/full-page-search-below-search-header/semantic-search.gjs @@ -4,7 +4,6 @@ import { on } from "@ember/modifier"; import { action } from "@ember/object"; import didUpdate from "@ember/render-modifiers/modifiers/did-update"; import { service } from "@ember/service"; -import { not } from "truth-helpers"; import DToggleSwitch from "discourse/components/d-toggle-switch"; import { SEARCH_TYPE_DEFAULT } from "discourse/controllers/full-page-search"; import { ajax } from "discourse/lib/ajax"; @@ -212,7 +211,7 @@ export default class SemanticSearch extends Component { - {{#if (not this.validSearchOrder)}} + {{#unless this.validSearchOrder}} - {{/if}} + {{/unless}} From 5e850071ad3616d58379551d88604e15b2d0f932 Mon Sep 17 00:00:00 2001 From: Mark VanLandingham Date: Mon, 16 Dec 2024 14:11:10 -0600 Subject: [PATCH 3/4] Fix up stuffs --- .../semantic-search.gjs | 71 +++++++++---------- .../embeddings/common/semantic-search.scss | 3 +- config/locales/client.en.yml | 2 +- lib/embeddings/entry_point.rb | 3 + plugin.rb | 2 - 5 files changed, 38 insertions(+), 43 deletions(-) diff --git a/assets/javascripts/discourse/connectors/full-page-search-below-search-header/semantic-search.gjs b/assets/javascripts/discourse/connectors/full-page-search-below-search-header/semantic-search.gjs index abc989fa0..1e3d84a3c 100644 --- a/assets/javascripts/discourse/connectors/full-page-search-below-search-header/semantic-search.gjs +++ b/assets/javascripts/discourse/connectors/full-page-search-below-search-header/semantic-search.gjs @@ -32,7 +32,7 @@ export default class SemanticSearch extends Component { constructor() { super(...arguments); this.appEvents.on("full-page-search:trigger-search", this, this.onSearch); - this.handleSearch(); + this.onSearch(); } willDestroy() { @@ -42,10 +42,14 @@ export default class SemanticSearch extends Component { @action onSearch() { - if (!this.searching) { - this.resetAiResults(); - return this.performHyDESearch(); + if (!this.searchEnabled) { + return; } + + this.initialSearchTerm = this.args.outletArgs.search; + this.searching = true; + this.resetAiResults(); + return this.performHyDESearch(); } get disableToggleSwitch() { @@ -69,30 +73,28 @@ export default class SemanticSearch extends Component { ); } - // Search results: - if (this.AiResults.length > 0) { - if (this.showingAiResults) { - return I18n.t( - "discourse_ai.embeddings.semantic_search_results.toggle", - { - count: this.AiResults.length, - } - ); - } else { - return I18n.t( - "discourse_ai.embeddings.semantic_search_results.toggle_hidden", - { - count: this.AiResults.length, - } - ); - } - } - // Search loading: if (this.searching) { return I18n.t("discourse_ai.embeddings.semantic_search_loading"); } + // We have results and we are showing them + if (this.AiResults.length && this.showingAiResults) { + return I18n.t("discourse_ai.embeddings.semantic_search_results.toggle", { + count: this.AiResults.length, + }); + } + + // We have results but are hiding them + if (this.AiResults.length && !this.showingAiResults) { + return I18n.t( + "discourse_ai.embeddings.semantic_search_results.toggle_hidden", + { + count: this.AiResults.length, + } + ); + } + // Typing to search: if ( this.AiResults.length === 0 && @@ -140,18 +142,6 @@ export default class SemanticSearch extends Component { this.args.outletArgs.addSearchResults([], "topic_id"); } - @action - handleSearch() { - if (!this.searchEnabled) { - return; - } - - if (this.initialSearchTerm) { - this.searching = true; - return this.performHyDESearch(); - } - } - performHyDESearch() { this.resetAiResults(); @@ -182,7 +172,7 @@ export default class SemanticSearch extends Component { this.sortOrder = this.args.outletArgs.sortOrder; if (this.validSearchOrder) { - this.handleSearch(); + this.onSearch(); } else { this.showingAiResults = false; this.resetAiResults(); @@ -195,7 +185,9 @@ export default class SemanticSearch extends Component {
- + {{#if this.validSearchOrder}} + + {{/if}} {{#unless this.validSearchOrder}} <:trigger> {{icon "far-circle-question"}} diff --git a/assets/stylesheets/modules/embeddings/common/semantic-search.scss b/assets/stylesheets/modules/embeddings/common/semantic-search.scss index bf820d139..673c0785d 100644 --- a/assets/stylesheets/modules/embeddings/common/semantic-search.scss +++ b/assets/stylesheets/modules/embeddings/common/semantic-search.scss @@ -16,7 +16,8 @@ display: flex; align-items: center; - &.in-progress { + &.in-progress, + &.unavailable { .semantic-search__searching-text { color: var(--primary-medium); } diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml index 39f559360..14c9db6b6 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -484,7 +484,7 @@ en: none: "Sorry, our AI search found no matching topics" new: "Press 'search' to begin looking for new results with AI" unavailable: "AI results unavailable" - semantic_search_unavailable_tooltip: "Search results must be sorted by Relevance to display AI results." + semantic_search_unavailable_tooltip: "Search results must be sorted by Relevance to display AI results" ai_generated_result: "Search result found using AI" quick_search: suffix: "in all topics and posts with AI" diff --git a/lib/embeddings/entry_point.rb b/lib/embeddings/entry_point.rb index 47df06fc7..ca1011f60 100644 --- a/lib/embeddings/entry_point.rb +++ b/lib/embeddings/entry_point.rb @@ -4,6 +4,9 @@ module DiscourseAi module Embeddings class EntryPoint def inject_into(plugin) + # far-circle-question used by semantic search unavailable tooltip + register_svg_icon "far-circle-question" if respond_to?(:register_svg_icon) + # Include random topics in the suggested list *only* if there are no related topics. plugin.register_modifier( :topic_view_suggested_topics_options, diff --git a/plugin.rb b/plugin.rb index bf8d20c03..aac3eee31 100644 --- a/plugin.rb +++ b/plugin.rb @@ -43,8 +43,6 @@ register_asset "stylesheets/modules/ai-bot/common/ai-artifact.scss" -register_svg_icon "far-circle-question" if respond_to?(:register_svg_icon) - module ::DiscourseAi PLUGIN_NAME = "discourse-ai" From 869008c65bdaf42a531001867ad080745a7b54b8 Mon Sep 17 00:00:00 2001 From: Mark VanLandingham Date: Mon, 16 Dec 2024 14:15:14 -0600 Subject: [PATCH 4/4] register icon correctly woops --- lib/embeddings/entry_point.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/embeddings/entry_point.rb b/lib/embeddings/entry_point.rb index ca1011f60..66047af2b 100644 --- a/lib/embeddings/entry_point.rb +++ b/lib/embeddings/entry_point.rb @@ -5,7 +5,7 @@ module Embeddings class EntryPoint def inject_into(plugin) # far-circle-question used by semantic search unavailable tooltip - register_svg_icon "far-circle-question" if respond_to?(:register_svg_icon) + plugin.register_svg_icon "far-circle-question" if plugin.respond_to?(:register_svg_icon) # Include random topics in the suggested list *only* if there are no related topics. plugin.register_modifier(