Skip to content

Commit

Permalink
DEV: Move semantic search from connector to component
Browse files Browse the repository at this point in the history
  • Loading branch information
markvanlan committed Jan 2, 2025
1 parent 11d0f60 commit 6050908
Show file tree
Hide file tree
Showing 2 changed files with 232 additions and 219 deletions.
224 changes: 224 additions & 0 deletions assets/javascripts/discourse/components/ai-semantic-search.gjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,224 @@
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 { ajax } from "discourse/lib/ajax";
import { isValidSearchTerm, translateResults } from "discourse/lib/search";
import icon from "discourse-common/helpers/d-icon";
import I18n, { i18n } from "discourse-i18n";
import DTooltip from "float-kit/components/d-tooltip";
import AiIndicatorWave from "./ai-indicator-wave";

export default class AiSemanticSearch 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.t(
"discourse_ai.embeddings.semantic_search_results.unavailable"
);
}

// 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 &&
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.searchTerm) {
this.initialSearchTerm = undefined;
}

return this.args.searchTerm;
}

get searchEnabled() {
return (
this.args.searchType === SEARCH_TYPE_DEFAULT &&
isValidSearchTerm(this.searchTerm, this.siteSettings) &&
this.validSearchOrder
);
}

@action
toggleAiResults() {
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");
}

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();
}
}
}

<template>
<span {{didUpdate this.sortChanged @sortOrder}}></span>
<div class="semantic-search__container search-results" role="region">
<div class="semantic-search__results">
<div
class="semantic-search__searching
{{if this.searching 'in-progress'}}
{{unless this.validSearchOrder 'unavailable'}}"
>
<DToggleSwitch
disabled={{this.disableToggleSwitch}}
@state={{this.showingAiResults}}
class="semantic-search__results-toggle"
{{on "click" this.toggleAiResults}}
/>

<div class="semantic-search__searching-text">
{{icon "discourse-sparkles"}}
{{this.searchStateText}}
</div>

{{#if this.validSearchOrder}}
<AiIndicatorWave @loading={{this.searching}} />
{{/if}}

{{#unless this.validSearchOrder}}

<DTooltip
@identifier="semantic-search-unavailable-tooltip"
class="semantic-search__unavailable-tooltip"
>
<:trigger>
{{icon "far-circle-question"}}
</:trigger>
<:content>
{{i18n
"discourse_ai.embeddings.semantic_search_unavailable_tooltip"
}}
</:content>
</DTooltip>
{{/unless}}
</div>
</div>
</div>
</template>
}
Loading

0 comments on commit 6050908

Please sign in to comment.