SearchFacade.java
package com.taxonomy.search.service;
import com.taxonomy.catalog.service.SearchService;
import com.taxonomy.catalog.service.TaxonomyService;
import com.taxonomy.dto.GraphSearchResult;
import com.taxonomy.dto.TaxonomyNodeDto;
import com.taxonomy.relations.service.GraphSearchService;
import com.taxonomy.relations.service.HybridSearchService;
import com.taxonomy.search.LocalOnnxIndexInitializer;
import com.taxonomy.shared.service.LocalEmbeddingService;
import com.taxonomy.workspace.service.WorkspaceContext;
import org.springframework.stereotype.Service;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
/** High-level search facade; graph relation visibility is explicitly workspace-scoped. */
@Service
public class SearchFacade {
private static final int MAX_SEARCH_RESULTS = 1_000;
private final TaxonomyService taxonomyService;
private final SearchService searchService;
private final HybridSearchService hybridSearchService;
private final LocalEmbeddingService embeddingService;
private final GraphSearchService graphSearchService;
private final LocalOnnxIndexInitializer embeddingIndexInitializer;
public SearchFacade(TaxonomyService taxonomyService,
SearchService searchService,
HybridSearchService hybridSearchService,
LocalEmbeddingService embeddingService,
GraphSearchService graphSearchService,
LocalOnnxIndexInitializer embeddingIndexInitializer) {
this.taxonomyService = taxonomyService;
this.searchService = searchService;
this.hybridSearchService = hybridSearchService;
this.embeddingService = embeddingService;
this.graphSearchService = graphSearchService;
this.embeddingIndexInitializer = embeddingIndexInitializer;
}
public boolean isInitialized() {
return taxonomyService.isInitialized();
}
public String getInitStatus() {
return taxonomyService.getInitStatus();
}
public List<TaxonomyNodeDto> fullTextSearch(String query, int maxResults) {
int resultLimit = boundedResults(maxResults);
if (resultLimit == 0) {
return List.of();
}
return searchService.search(query, resultLimit);
}
public List<TaxonomyNodeDto> semanticSearch(String query, int maxResults) {
int resultLimit = boundedResults(maxResults);
if (resultLimit == 0) {
return List.of();
}
return embeddingService.semanticSearch(query, resultLimit);
}
public List<TaxonomyNodeDto> hybridSearch(String query, int maxResults) {
int resultLimit = boundedResults(maxResults);
if (resultLimit == 0) {
return List.of();
}
return hybridSearchService.hybridSearch(query, resultLimit);
}
public List<TaxonomyNodeDto> findSimilarNodes(String code, int topK) {
int resultLimit = boundedResults(topK);
if (resultLimit == 0) {
return List.of();
}
return embeddingService.findSimilarNodes(code, resultLimit);
}
static int boundedResults(int requestedResults) {
return Math.max(0, Math.min(requestedResults, MAX_SEARCH_RESULTS));
}
public GraphSearchResult graphSearch(String query,
int maxResults,
WorkspaceContext workspaceContext) {
return graphSearchService.graphSearch(query, maxResults, workspaceContext);
}
public boolean isSemanticSearchReady() {
return embeddingService.isEnabled()
&& embeddingService.isAvailable()
&& embeddingIndexInitializer.isNodeSearchReady();
}
public Map<String, Object> getEmbeddingStatus() {
boolean semanticReady = isSemanticSearchReady();
Map<String, Object> status = new LinkedHashMap<>();
status.put("enabled", embeddingService.isEnabled());
// Backward-compatible field consumed by the current browser UI. It now
// means actually searchable, not merely "no model failure observed yet".
status.put("available", semanticReady);
status.put("modelAvailable", embeddingService.isAvailable());
status.put("modelUrl", embeddingService.effectiveModelUrl());
status.put("indexedNodes", embeddingService.indexedNodeCount());
status.put("semanticReady", semanticReady);
status.put("indexState", embeddingIndexInitializer.getState().name());
status.put("indexDetail", embeddingIndexInitializer.getDetail());
status.put("indexedNodesAtReadiness",
embeddingIndexInitializer.getIndexedNodesAtReadiness());
return status;
}
}