import { inject, Injectable, signal } from '@angular/core'; import { HttpClient, HttpParams } from '@angular/common/http'; import { Observable } from 'rxjs'; import { finalize } from 'rxjs/operators'; /** * Search hit from Meilisearch with highlighting */ export interface SearchHit { id: string; title: string; path: string; file: string; tags?: string[]; properties?: Record; updatedAt?: number; createdAt?: number; excerpt?: string; _formatted?: { title?: string; content?: string; }; } /** * Search response from Meilisearch API */ export interface SearchResponse { hits: SearchHit[]; estimatedTotalHits: number; facetDistribution?: Record>; processingTimeMs: number; query: string; } /** * Search options */ export interface SearchOptions { limit?: number; offset?: number; sort?: string; highlight?: boolean; } /** * Service for server-side search using Meilisearch */ @Injectable({ providedIn: 'root' }) export class SearchMeilisearchService { private http = inject(HttpClient); /** Loading state signal */ loading = signal(false); /** * Execute a search query */ search(q: string, opts?: SearchOptions): Observable { this.loading.set(true); let params = new HttpParams().set('q', q); if (opts?.limit !== undefined) { params = params.set('limit', String(opts.limit)); } if (opts?.offset !== undefined) { params = params.set('offset', String(opts.offset)); } if (opts?.sort) { params = params.set('sort', opts.sort); } if (opts?.highlight === false) { params = params.set('highlight', 'false'); } const url = `/api/search?${params.toString()}`; console.log('[SearchMeilisearchService] Sending request:', url); return this.http .get('/api/search', { params }) .pipe( finalize(() => { console.log('[SearchMeilisearchService] Request completed'); this.loading.set(false); }) ); } /** * Trigger a full reindex of the vault */ reindex(): Observable<{ ok: boolean; indexed: boolean; count: number; elapsedMs: number }> { this.loading.set(true); return this.http .post<{ ok: boolean; indexed: boolean; count: number; elapsedMs: number }>( '/api/reindex', {} ) .pipe(finalize(() => this.loading.set(false))); } }