diff --git a/assets/javascripts/discourse/components/ai-full-page-semantic-search.gjs b/assets/javascripts/discourse/components/ai-full-page-semantic-search.gjs new file mode 100644 index 00000000..046bc6de --- /dev/null +++ b/assets/javascripts/discourse/components/ai-full-page-semantic-search.gjs @@ -0,0 +1,179 @@ +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 { inject as service } from "@ember/service"; +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 AiIndicatorWave from "./ai-indicator-wave"; + +export default class AiFullPageSemanticSearch extends Component { + @service appEvents; + @service siteSettings; + @service searchPreferencesManager; + + @tracked searching = false; + @tracked AIResults = []; + @tracked showingAIResults = false; + initialSearchTerm = this.args.outletArgs.search; + + get disableToggleSwitch() { + if ( + this.searching || + this.AIResults.length === 0 || + this.args.outletArgs.sortOrder !== 0 + ) { + return true; + } + } + + get searchStateText() { + // 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"); + } + + // Typing to search: + if ( + this.AIResults.length === 0 && + this.searchTerm !== this.initialSearchTerm + ) { + return I18n.t("discourse_ai.embeddings.semantic_search_results.new"); + } + + // No results: + if (this.AIResults.length === 0) { + return I18n.t("discourse_ai.embeddings.semantic_search_results.none"); + } + } + + get searchTerm() { + if (this.initialSearchTerm !== this.args.outletArgs.search) { + this.initialSearchTerm = undefined; + } + + return this.args.outletArgs.search; + } + + get searchEnabled() { + return ( + this.args.outletArgs.type === SEARCH_TYPE_DEFAULT && + isValidSearchTerm(this.searchTerm, this.siteSettings) && + this.args.outletArgs.sortOrder === 0 + ); + } + + @action + toggleAIResults() { + if (this.showingAIResults) { + this.args.outletArgs.addSearchResults([], "topic_id"); + } else { + this.args.outletArgs.addSearchResults(this.AIResults, "topic_id"); + } + this.showingAIResults = !this.showingAIResults; + } + + @action + resetAIResults() { + this.AIResults = []; + this.showingAIResults = false; + this.args.outletArgs.addSearchResults([], "topic_id"); + } + + @action + handleSearch() { + if (!this.searchEnabled) { + return; + } + + if (this.initialSearchTerm && !this.searching) { + return this.performHyDESearch(); + } + + withPluginApi("1.15.0", (api) => { + api.onAppEvent("full-page-search:trigger-search", () => { + if (!this.searching) { + this.resetAIResults(); + return this.performHyDESearch(); + } + }); + }); + } + + performHyDESearch() { + this.searching = true; + this.resetAIResults(); + + ajax("/discourse-ai/embeddings/semantic-search", { + data: { q: this.searchTerm }, + }) + .then(async (results) => { + const model = (await translateResults(results)) || {}; + + if (model.posts?.length === 0) { + this.searching = false; + return; + } + + model.posts.forEach((post) => { + post.generatedByAI = true; + }); + + this.AIResults = model.posts; + }) + .finally(() => (this.searching = false)); + } + + + {{#if this.searchEnabled}} + + + + + + + {{icon "discourse-sparkles"}} + {{this.searchStateText}} + + + + + + + {{/if}} + +} 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 ba1c4263..96659d11 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 @@ -1,183 +1,12 @@ 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 { inject as service } from "@ember/service"; -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 AiIndicatorWave from "../../components/ai-indicator-wave"; +import AIFullPageSemanticSearch from "discourse/plugins/discourse-ai/discourse/components/ai-full-page-semantic-search"; export default class SemanticSearch extends Component { static shouldRender(_args, { siteSettings }) { return siteSettings.ai_embeddings_semantic_search_enabled; } - @service appEvents; - @service siteSettings; - @service searchPreferencesManager; - - @tracked searching = false; - @tracked AIResults = []; - @tracked showingAIResults = false; - initialSearchTerm = this.args.outletArgs.search; - - get disableToggleSwitch() { - if ( - this.searching || - this.AIResults.length === 0 || - this.args.outletArgs.sortOrder !== 0 - ) { - return true; - } - } - - get searchStateText() { - // 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"); - } - - // Typing to search: - if ( - this.AIResults.length === 0 && - this.searchTerm !== this.initialSearchTerm - ) { - return I18n.t("discourse_ai.embeddings.semantic_search_results.new"); - } - - // No results: - if (this.AIResults.length === 0) { - return I18n.t("discourse_ai.embeddings.semantic_search_results.none"); - } - } - - get searchTerm() { - if (this.initialSearchTerm !== this.args.outletArgs.search) { - this.initialSearchTerm = undefined; - } - - return this.args.outletArgs.search; - } - - get searchEnabled() { - return ( - this.args.outletArgs.type === SEARCH_TYPE_DEFAULT && - isValidSearchTerm(this.searchTerm, this.siteSettings) && - this.args.outletArgs.sortOrder === 0 - ); - } - - @action - toggleAIResults() { - if (this.showingAIResults) { - this.args.outletArgs.addSearchResults([], "topic_id"); - } else { - this.args.outletArgs.addSearchResults(this.AIResults, "topic_id"); - } - this.showingAIResults = !this.showingAIResults; - } - - @action - resetAIResults() { - this.AIResults = []; - this.showingAIResults = false; - this.args.outletArgs.addSearchResults([], "topic_id"); - } - - @action - handleSearch() { - if (!this.searchEnabled) { - return; - } - - if (this.initialSearchTerm && !this.searching) { - return this.performHyDESearch(); - } - - withPluginApi("1.15.0", (api) => { - api.onAppEvent("full-page-search:trigger-search", () => { - if (!this.searching) { - this.resetAIResults(); - return this.performHyDESearch(); - } - }); - }); - } - - performHyDESearch() { - this.searching = true; - this.resetAIResults(); - - ajax("/discourse-ai/embeddings/semantic-search", { - data: { q: this.searchTerm }, - }) - .then(async (results) => { - const model = (await translateResults(results)) || {}; - - if (model.posts?.length === 0) { - this.searching = false; - return; - } - - model.posts.forEach((post) => { - post.generatedByAI = true; - }); - - this.AIResults = model.posts; - }) - .finally(() => (this.searching = false)); - } - - {{#if this.searchEnabled}} - - - - - - - {{icon "discourse-sparkles"}} - {{this.searchStateText}} - - - - - - - {{/if}} + }