import Component from "@glimmer/component"; import { tracked } from "@glimmer/tracking"; 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 DToggleSwitch from "discourse/components/d-toggle-switch"; import { SEARCH_TYPE_DEFAULT } from "discourse/controllers/full-page-search"; import concatClass from "discourse/helpers/concat-class"; import { ajax } from "discourse/lib/ajax"; import { isValidSearchTerm, translateResults } from "discourse/lib/search"; import icon from "discourse-common/helpers/d-icon"; import { i18n } from "discourse-i18n"; import DTooltip from "float-kit/components/d-tooltip"; import AiIndicatorWave from "./ai-indicator-wave"; const AI_RESULTS_TOGGLED = "full-page-search:ai-results-toggled"; export default class AiFullPageSearch extends Component { @service appEvents; @service router; @service siteSettings; @service searchPreferencesManager; @tracked searching; @tracked AiResults = []; @tracked showingAiResults = false; @tracked sortOrder = this.args.sortOrder; initialSearchTerm = this.args.searchTerm; constructor() { super(...arguments); this.appEvents.on("full-page-search:trigger-search", this, this.onSearch); this.onSearch(); } willDestroy() { super.willDestroy(...arguments); this.appEvents.off("full-page-search:trigger-search", this, this.onSearch); } @action onSearch() { if (!this.searchEnabled) { return; } this.initialSearchTerm = this.args.searchTerm; this.searching = true; this.resetAiResults(); return this.performHyDESearch(); } get disableToggleSwitch() { if ( this.searching || this.AiResults.length === 0 || !this.validSearchOrder ) { return true; } } get validSearchOrder() { return this.sortOrder === 0; } get searchStateText() { if (!this.validSearchOrder) { return i18n( "discourse_ai.embeddings.semantic_search_results.unavailable" ); } // Search loading: if (this.searching) { return i18n("discourse_ai.embeddings.semantic_search_loading"); } // We have results and we are showing them if (this.AiResults.length && this.showingAiResults) { return i18n("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( "discourse_ai.embeddings.semantic_search_results.toggle_hidden", { count: this.AiResults.length, } ); } // Typing to search: if ( this.AiResults.length === 0 && this.searchTerm !== this.initialSearchTerm ) { return i18n("discourse_ai.embeddings.semantic_search_results.new"); } // No results: if (this.AiResults.length === 0) { return i18n("discourse_ai.embeddings.semantic_search_results.none"); } } get settled() { return ( this.validSearchOrder && !this.searching && this.searchTerm === this.initialSearchTerm ); } get noResults() { return this.settled && this.AiResults.length === 0; } get searchTerm() { if (this.initialSearchTerm !== this.args.searchTerm) { this.initialSearchTerm = undefined; } return this.args.searchTerm; } get searchEnabled() { return ( this.args.searchType === SEARCH_TYPE_DEFAULT && isValidSearchTerm(this.searchTerm, this.siteSettings) && this.validSearchOrder ); } get searchClass() { if (!this.validSearchOrder) { return "unavailable"; } else if (this.searching) { return "in-progress"; } else if (this.noResults) { return "no-results"; } } get tooltipText() { return i18n( `discourse_ai.embeddings.semantic_search_tooltips.${ this.validSearchOrder ? "results_explanation" : "invalid_sort" }` ); } @action toggleAiResults() { this.appEvents.trigger(AI_RESULTS_TOGGLED, { enabled: !this.showingAiResults, }); if (this.showingAiResults) { this.args.addSearchResults([], "topic_id"); } else { this.args.addSearchResults(this.AiResults, "topic_id"); } this.showingAiResults = !this.showingAiResults; } @action resetAiResults() { this.AiResults = []; this.showingAiResults = false; this.args.addSearchResults([], "topic_id"); this.appEvents.trigger(AI_RESULTS_TOGGLED, { enabled: false, }); } performHyDESearch() { 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) { return; } model.posts.forEach((post) => { post.generatedByAi = true; }); this.AiResults = model.posts; }) .finally(() => { this.searching = false; }); } @action sortChanged() { if (this.sortOrder !== this.args.sortOrder) { this.sortOrder = this.args.sortOrder; if (this.validSearchOrder) { this.onSearch(); } else { this.showingAiResults = false; this.resetAiResults(); } } } }