/** * Template search helpers for document-generator. * * Provides a two-tier search: * 1. Primary: KLAUSUR_SERVICE (curated legal templates with full metadata) * 2. Fallback: RAG proxy (ai-compliance-sdk regulation search) */ import type { LegalTemplateResult } from '@/lib/sdk/types' export const KLAUSUR_SERVICE_URL = process.env.NEXT_PUBLIC_KLAUSUR_SERVICE_URL || 'http://localhost:8086' export const RAG_PROXY = '/api/sdk/v1/rag' export interface TemplateSearchParams { query: string templateType?: string licenseTypes?: string[] language?: 'de' | 'en' jurisdiction?: string limit?: number } /** * Search for legal templates with automatic RAG fallback. * * Tries KLAUSUR_SERVICE first (5 s timeout). If unavailable or returning an * error, falls back to the RAG proxy served by ai-compliance-sdk. * Returns an empty array if both services are down. */ export async function searchTemplates( params: TemplateSearchParams ): Promise { // 1. Primary: KLAUSUR_SERVICE try { const res = await fetch(`${KLAUSUR_SERVICE_URL}/api/v1/admin/templates/search`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ query: params.query, template_type: params.templateType, license_types: params.licenseTypes, language: params.language, jurisdiction: params.jurisdiction, limit: params.limit || 10, }), signal: AbortSignal.timeout(5000), }) if (res.ok) { const data = await res.json() return data.map((r: any) => ({ id: r.id, score: r.score, text: r.text, documentTitle: r.document_title, templateType: r.template_type, clauseCategory: r.clause_category, language: r.language, jurisdiction: r.jurisdiction, licenseId: r.license_id, licenseName: r.license_name, licenseUrl: r.license_url, attributionRequired: r.attribution_required, attributionText: r.attribution_text, sourceName: r.source_name, sourceUrl: r.source_url, sourceRepo: r.source_repo, placeholders: r.placeholders || [], isCompleteDocument: r.is_complete_document, isModular: r.is_modular, requiresCustomization: r.requires_customization, outputAllowed: r.output_allowed ?? true, modificationAllowed: r.modification_allowed ?? true, distortionProhibited: r.distortion_prohibited ?? false, })) } } catch { // KLAUSUR_SERVICE not reachable — fall through to RAG } // 2. Fallback: RAG proxy try { const res = await fetch(`${RAG_PROXY}/search`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ query: params.query || '', limit: params.limit || 10 }), }) if (res.ok) { const data = await res.json() return (data.results || []).map((r: any, i: number) => ({ id: r.regulation_code || `rag-${i}`, score: r.score ?? 0.5, text: r.text || '', documentTitle: r.regulation_name || r.regulation_short || 'Dokument', templateType: 'regulation', clauseCategory: null, language: 'de', jurisdiction: 'eu', licenseId: null, licenseName: null, licenseUrl: null, attributionRequired: false, attributionText: null, sourceName: 'RAG', sourceUrl: null, sourceRepo: null, placeholders: [], isCompleteDocument: false, isModular: true, requiresCustomization: true, outputAllowed: true, modificationAllowed: true, distortionProhibited: false, source: 'rag' as const, })) } } catch { // both services failed } return [] } export async function getTemplatesStatus(): Promise { const response = await fetch(`${KLAUSUR_SERVICE_URL}/api/v1/admin/templates/status`, { signal: AbortSignal.timeout(5000), }) if (!response.ok) return null return response.json() } export async function getSources(): Promise { const response = await fetch(`${KLAUSUR_SERVICE_URL}/api/v1/admin/templates/sources`, { signal: AbortSignal.timeout(5000), }) if (!response.ok) return [] const data = await response.json() return data.sources || [] }