@open-mercato/search 0.4.6-develop-e321a4e2a1 → 0.4.6-main-24e64eef39

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/di.js.map CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/di.ts"],
4
- "sourcesContent": ["import { asValue } from 'awilix'\nimport type { Knex } from 'knex'\nimport { SearchService } from './service'\nimport { TokenSearchStrategy } from './strategies/token.strategy'\nimport { VectorSearchStrategy, type EmbeddingService } from './strategies/vector.strategy'\nimport { FullTextSearchStrategy } from './strategies/fulltext.strategy'\nimport { createFulltextDriver } from './fulltext/drivers'\nimport { SearchIndexer } from './indexer/search-indexer'\nimport type {\n SearchStrategy,\n ResultMergeConfig,\n SearchModuleConfig,\n SearchFieldPolicy,\n SearchEntityConfig,\n PresenterEnricherFn,\n} from './types'\nimport type { VectorDriver } from './vector/types'\nimport type { QueryEngine } from '@open-mercato/shared/lib/query/types'\nimport type { EntityId } from '@open-mercato/shared/modules/entities'\nimport type { SearchStrategyId } from '@open-mercato/shared/modules/search'\nimport type { Queue } from '@open-mercato/queue'\nimport type { FulltextIndexJobPayload } from './queue/fulltext-indexing'\nimport type { VectorIndexJobPayload } from './queue/vector-indexing'\nimport type { EncryptionMapEntry } from './lib/field-policy'\nimport type { TenantDataEncryptionService } from '@open-mercato/shared/lib/encryption/tenantDataEncryptionService'\nimport { createPresenterEnricher } from './lib/presenter-enricher'\n\n/**\n * Check if encrypted fields should be excluded from search indexing.\n * Controlled by SEARCH_EXCLUDE_ENCRYPTED_FIELDS environment variable.\n * Default: false (index all fields including decrypted data)\n */\nfunction shouldExcludeEncryptedFields(): boolean {\n const raw = (process.env.SEARCH_EXCLUDE_ENCRYPTED_FIELDS ?? '').toLowerCase()\n return raw === '1' || raw === 'true' || raw === 'yes' || raw === 'on'\n}\n\n/**\n * Create an encryption map resolver that queries the database.\n * Falls back to empty array if query fails.\n */\nfunction createEncryptionMapResolver(\n knex: Knex,\n): (entityId: EntityId) => Promise<EncryptionMapEntry[]> {\n // Cache encryption maps per entity to avoid repeated queries\n const cache = new Map<string, { entries: EncryptionMapEntry[]; expiresAt: number }>()\n const CACHE_TTL_MS = 5 * 60 * 1000 // 5 minutes\n\n return async (entityId: EntityId): Promise<EncryptionMapEntry[]> => {\n const cached = cache.get(entityId)\n if (cached && cached.expiresAt > Date.now()) {\n return cached.entries\n }\n\n try {\n const rows = await knex('encryption_maps')\n .select('fields_json')\n .where('entity_id', entityId)\n .where('is_active', true)\n .whereNull('deleted_at')\n .first()\n\n const fieldsJson = rows?.fields_json\n const entries: EncryptionMapEntry[] = Array.isArray(fieldsJson)\n ? fieldsJson.map((f: { field: string; hashField?: string | null }) => ({\n field: f.field,\n hashField: f.hashField ?? null,\n }))\n : []\n\n cache.set(entityId, { entries, expiresAt: Date.now() + CACHE_TTL_MS })\n return entries\n } catch {\n // Query failed, return empty array (don't exclude any fields)\n return []\n }\n }\n}\n\n/**\n * Container interface - minimal subset needed for registration.\n */\nexport interface SearchContainer {\n resolve<T = unknown>(name: string): T\n register(registrations: Record<string, unknown>): void\n}\n\n/**\n * Configuration options for search module registration.\n */\nexport type SearchModuleOptions = {\n /** Override default strategies to use */\n defaultStrategies?: SearchStrategyId[]\n /** Override merge configuration */\n mergeConfig?: ResultMergeConfig\n /** Skip token strategy registration */\n skipTokens?: boolean\n /** Skip vector strategy registration */\n skipVector?: boolean\n /** Skip fulltext strategy registration */\n skipFulltext?: boolean\n /** Module configurations (from generated/search.generated.ts) */\n moduleConfigs?: SearchModuleConfig[]\n}\n\n/**\n * Register the search module in the DI container.\n *\n * This creates and registers:\n * - SearchService instance\n * - All configured search strategies\n *\n * @param container - Awilix container\n * @param options - Optional configuration overrides\n */\nexport function registerSearchModule(\n container: SearchContainer,\n options?: SearchModuleOptions,\n): void {\n const strategies: SearchStrategy[] = []\n\n // Token strategy (always available unless explicitly skipped)\n if (!options?.skipTokens) {\n try {\n const em = container.resolve<{ getConnection: () => { getKnex: () => Knex } }>('em')\n const knex = em.getConnection().getKnex()\n strategies.push(new TokenSearchStrategy(knex))\n } catch {\n // knex not available via em, skipping TokenSearchStrategy\n }\n }\n\n // Vector strategy (requires embedding service and driver)\n // Note: We register even if not currently available - availability is checked at search time\n // via isAvailable(). The embedding config may be loaded later from the database.\n if (!options?.skipVector) {\n try {\n const embeddingService = container.resolve<EmbeddingService>('vectorEmbeddingService')\n const drivers = container.resolve<VectorDriver[]>('vectorDrivers')\n const primaryDriver = drivers?.[0]\n\n if (embeddingService && primaryDriver) {\n strategies.push(new VectorSearchStrategy(embeddingService, primaryDriver))\n }\n } catch {\n // Vector module not available, skipping VectorSearchStrategy\n }\n }\n\n // Build entity config map for field policy resolution\n const entityConfigMap = new Map<EntityId, SearchEntityConfig>()\n for (const moduleConfig of (options?.moduleConfigs ?? [])) {\n for (const entityConfig of moduleConfig.entities) {\n if (entityConfig.enabled !== false) {\n entityConfigMap.set(entityConfig.entityId as EntityId, entityConfig)\n }\n }\n }\n\n // Fulltext strategy (requires driver configuration, e.g., MEILISEARCH_HOST)\n if (!options?.skipFulltext) {\n // Build encryption map resolver if SEARCH_EXCLUDE_ENCRYPTED_FIELDS is enabled\n let encryptionMapResolver: ((entityId: EntityId) => Promise<EncryptionMapEntry[]>) | undefined\n if (shouldExcludeEncryptedFields()) {\n try {\n const em = container.resolve<{ getConnection: () => { getKnex: () => Knex } }>('em')\n const knex = em.getConnection().getKnex()\n encryptionMapResolver = createEncryptionMapResolver(knex)\n } catch {\n // Knex not available, encrypted field filtering disabled\n }\n }\n\n const fulltextDriver = createFulltextDriver({\n fieldPolicyResolver: (entityId: EntityId): SearchFieldPolicy | undefined => {\n const config = entityConfigMap.get(entityId)\n return config?.fieldPolicy\n },\n encryptionMapResolver,\n })\n\n if (fulltextDriver) {\n strategies.push(new FullTextSearchStrategy(fulltextDriver))\n }\n }\n\n // Determine default strategies based on what's available\n const defaultStrategies = options?.defaultStrategies ?? determineDefaultStrategies(strategies)\n\n // Try to resolve queryEngine for reindex support and presenter enrichment\n let queryEngine: QueryEngine | undefined\n try {\n queryEngine = container.resolve<QueryEngine>('queryEngine')\n } catch {\n // QueryEngine not available, reindex will be disabled\n }\n\n // Resolve encryption service for decrypting presenter data\n let encryptionService: TenantDataEncryptionService | null = null\n try {\n encryptionService = container.resolve<TenantDataEncryptionService>('tenantEncryptionService')\n } catch {\n // Encryption service not available, presenters won't be decrypted\n }\n\n // Create presenter enricher for database-based presenter resolution\n let presenterEnricher: PresenterEnricherFn | undefined\n try {\n const em = container.resolve<{ getConnection: () => { getKnex: () => Knex } }>('em')\n const knex = em.getConnection().getKnex()\n presenterEnricher = createPresenterEnricher(knex, entityConfigMap, queryEngine, encryptionService)\n } catch {\n // knex not available, presenter enrichment disabled\n }\n\n // Create search service\n const searchService = new SearchService({\n strategies,\n defaultStrategies,\n fallbackStrategy: 'tokens',\n mergeConfig: options?.mergeConfig ?? {\n duplicateHandling: 'highest_score',\n strategyWeights: {\n fulltext: 1.2,\n vector: 1.0,\n tokens: 0.8,\n },\n },\n presenterEnricher,\n })\n\n // Create search indexer with module configs\n const moduleConfigs = options?.moduleConfigs ?? []\n\n // Try to resolve fulltextIndexQueue for queue-based reindexing\n let fulltextQueue: Queue<FulltextIndexJobPayload> | undefined\n try {\n fulltextQueue = container.resolve<Queue<FulltextIndexJobPayload>>('fulltextIndexQueue')\n } catch {\n // Queue not available, queue-based fulltext reindex will be disabled\n }\n\n // Try to resolve vectorIndexQueue for queue-based vector reindexing\n let vectorQueue: Queue<VectorIndexJobPayload> | undefined\n try {\n vectorQueue = container.resolve<Queue<VectorIndexJobPayload>>('vectorIndexQueue')\n } catch {\n // Queue not available, queue-based vector reindex will be disabled\n }\n\n const searchIndexer = new SearchIndexer(searchService, moduleConfigs, {\n queryEngine,\n fulltextQueue,\n vectorQueue,\n })\n\n // Register in container\n container.register({\n searchService: asValue(searchService),\n searchStrategies: asValue(strategies),\n searchIndexer: asValue(searchIndexer),\n })\n}\n\n/**\n * Determine default strategy order based on available strategies.\n * Prefers fulltext > vector > tokens.\n */\nfunction determineDefaultStrategies(strategies: SearchStrategy[]): SearchStrategyId[] {\n const available = new Set(strategies.map((s) => s.id))\n const defaults: SearchStrategyId[] = []\n\n if (available.has('fulltext')) defaults.push('fulltext')\n if (available.has('vector')) defaults.push('vector')\n if (available.has('tokens')) defaults.push('tokens')\n\n return defaults.length > 0 ? defaults : ['tokens']\n}\n\n/**\n * Helper to add a custom strategy to an existing SearchService.\n *\n * @param container - DI container\n * @param strategy - Strategy to add\n */\nexport function addSearchStrategy(container: SearchContainer, strategy: SearchStrategy): void {\n const service = container.resolve<SearchService>('searchService')\n service.registerStrategy(strategy)\n\n const strategies = container.resolve<SearchStrategy[]>('searchStrategies')\n strategies.push(strategy)\n}\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { asValue } from 'awilix'\nimport type { Knex } from 'knex'\nimport { SearchService } from './service'\nimport { TokenSearchStrategy } from './strategies/token.strategy'\nimport { VectorSearchStrategy, type EmbeddingService } from './strategies/vector.strategy'\nimport { FullTextSearchStrategy } from './strategies/fulltext.strategy'\nimport { createFulltextDriver } from './fulltext/drivers'\nimport { SearchIndexer } from './indexer/search-indexer'\nimport type {\n SearchStrategy,\n ResultMergeConfig,\n SearchModuleConfig,\n SearchFieldPolicy,\n SearchEntityConfig,\n PresenterEnricherFn,\n} from './types'\nimport type { VectorDriver } from './vector/types'\nimport type { QueryEngine } from '@open-mercato/shared/lib/query/types'\nimport type { EntityId } from '@open-mercato/shared/modules/entities'\nimport type { Queue } from '@open-mercato/queue'\nimport type { FulltextIndexJobPayload } from './queue/fulltext-indexing'\nimport type { VectorIndexJobPayload } from './queue/vector-indexing'\nimport type { EncryptionMapEntry } from './lib/field-policy'\nimport type { TenantDataEncryptionService } from '@open-mercato/shared/lib/encryption/tenantDataEncryptionService'\nimport { createPresenterEnricher } from './lib/presenter-enricher'\n\n/**\n * Check if encrypted fields should be excluded from search indexing.\n * Controlled by SEARCH_EXCLUDE_ENCRYPTED_FIELDS environment variable.\n * Default: false (index all fields including decrypted data)\n */\nfunction shouldExcludeEncryptedFields(): boolean {\n const raw = (process.env.SEARCH_EXCLUDE_ENCRYPTED_FIELDS ?? '').toLowerCase()\n return raw === '1' || raw === 'true' || raw === 'yes' || raw === 'on'\n}\n\n/**\n * Create an encryption map resolver that queries the database.\n * Falls back to empty array if query fails.\n */\nfunction createEncryptionMapResolver(\n knex: Knex,\n): (entityId: EntityId) => Promise<EncryptionMapEntry[]> {\n // Cache encryption maps per entity to avoid repeated queries\n const cache = new Map<string, { entries: EncryptionMapEntry[]; expiresAt: number }>()\n const CACHE_TTL_MS = 5 * 60 * 1000 // 5 minutes\n\n return async (entityId: EntityId): Promise<EncryptionMapEntry[]> => {\n const cached = cache.get(entityId)\n if (cached && cached.expiresAt > Date.now()) {\n return cached.entries\n }\n\n try {\n const rows = await knex('encryption_maps')\n .select('fields_json')\n .where('entity_id', entityId)\n .where('is_active', true)\n .whereNull('deleted_at')\n .first()\n\n const fieldsJson = rows?.fields_json\n const entries: EncryptionMapEntry[] = Array.isArray(fieldsJson)\n ? fieldsJson.map((f: { field: string; hashField?: string | null }) => ({\n field: f.field,\n hashField: f.hashField ?? null,\n }))\n : []\n\n cache.set(entityId, { entries, expiresAt: Date.now() + CACHE_TTL_MS })\n return entries\n } catch {\n // Query failed, return empty array (don't exclude any fields)\n return []\n }\n }\n}\n\n/**\n * Container interface - minimal subset needed for registration.\n */\nexport interface SearchContainer {\n resolve<T = unknown>(name: string): T\n register(registrations: Record<string, unknown>): void\n}\n\n/**\n * Configuration options for search module registration.\n */\nexport type SearchModuleOptions = {\n /** Override default strategies to use */\n defaultStrategies?: string[]\n /** Override merge configuration */\n mergeConfig?: ResultMergeConfig\n /** Skip token strategy registration */\n skipTokens?: boolean\n /** Skip vector strategy registration */\n skipVector?: boolean\n /** Skip fulltext strategy registration */\n skipFulltext?: boolean\n /** Module configurations (from generated/search.generated.ts) */\n moduleConfigs?: SearchModuleConfig[]\n}\n\n/**\n * Register the search module in the DI container.\n *\n * This creates and registers:\n * - SearchService instance\n * - All configured search strategies\n *\n * @param container - Awilix container\n * @param options - Optional configuration overrides\n */\nexport function registerSearchModule(\n container: SearchContainer,\n options?: SearchModuleOptions,\n): void {\n const strategies: SearchStrategy[] = []\n\n // Token strategy (always available unless explicitly skipped)\n if (!options?.skipTokens) {\n try {\n const em = container.resolve<{ getConnection: () => { getKnex: () => Knex } }>('em')\n const knex = em.getConnection().getKnex()\n strategies.push(new TokenSearchStrategy(knex))\n } catch {\n // knex not available via em, skipping TokenSearchStrategy\n }\n }\n\n // Vector strategy (requires embedding service and driver)\n // Note: We register even if not currently available - availability is checked at search time\n // via isAvailable(). The embedding config may be loaded later from the database.\n if (!options?.skipVector) {\n try {\n const embeddingService = container.resolve<EmbeddingService>('vectorEmbeddingService')\n const drivers = container.resolve<VectorDriver[]>('vectorDrivers')\n const primaryDriver = drivers?.[0]\n\n if (embeddingService && primaryDriver) {\n strategies.push(new VectorSearchStrategy(embeddingService, primaryDriver))\n }\n } catch {\n // Vector module not available, skipping VectorSearchStrategy\n }\n }\n\n // Build entity config map for field policy resolution\n const entityConfigMap = new Map<EntityId, SearchEntityConfig>()\n for (const moduleConfig of (options?.moduleConfigs ?? [])) {\n for (const entityConfig of moduleConfig.entities) {\n if (entityConfig.enabled !== false) {\n entityConfigMap.set(entityConfig.entityId as EntityId, entityConfig)\n }\n }\n }\n\n // Fulltext strategy (requires driver configuration, e.g., MEILISEARCH_HOST)\n if (!options?.skipFulltext) {\n // Build encryption map resolver if SEARCH_EXCLUDE_ENCRYPTED_FIELDS is enabled\n let encryptionMapResolver: ((entityId: EntityId) => Promise<EncryptionMapEntry[]>) | undefined\n if (shouldExcludeEncryptedFields()) {\n try {\n const em = container.resolve<{ getConnection: () => { getKnex: () => Knex } }>('em')\n const knex = em.getConnection().getKnex()\n encryptionMapResolver = createEncryptionMapResolver(knex)\n } catch {\n // Knex not available, encrypted field filtering disabled\n }\n }\n\n const fulltextDriver = createFulltextDriver({\n fieldPolicyResolver: (entityId: EntityId): SearchFieldPolicy | undefined => {\n const config = entityConfigMap.get(entityId)\n return config?.fieldPolicy\n },\n encryptionMapResolver,\n })\n\n if (fulltextDriver) {\n strategies.push(new FullTextSearchStrategy(fulltextDriver))\n }\n }\n\n // Determine default strategies based on what's available\n const defaultStrategies = options?.defaultStrategies ?? determineDefaultStrategies(strategies)\n\n // Try to resolve queryEngine for reindex support and presenter enrichment\n let queryEngine: QueryEngine | undefined\n try {\n queryEngine = container.resolve<QueryEngine>('queryEngine')\n } catch {\n // QueryEngine not available, reindex will be disabled\n }\n\n // Resolve encryption service for decrypting presenter data\n let encryptionService: TenantDataEncryptionService | null = null\n try {\n encryptionService = container.resolve<TenantDataEncryptionService>('tenantEncryptionService')\n } catch {\n // Encryption service not available, presenters won't be decrypted\n }\n\n // Create presenter enricher for database-based presenter resolution\n let presenterEnricher: PresenterEnricherFn | undefined\n try {\n const em = container.resolve<{ getConnection: () => { getKnex: () => Knex } }>('em')\n const knex = em.getConnection().getKnex()\n presenterEnricher = createPresenterEnricher(knex, entityConfigMap, queryEngine, encryptionService)\n } catch {\n // knex not available, presenter enrichment disabled\n }\n\n // Create search service\n const searchService = new SearchService({\n strategies,\n defaultStrategies,\n fallbackStrategy: 'tokens',\n mergeConfig: options?.mergeConfig ?? {\n duplicateHandling: 'highest_score',\n strategyWeights: {\n fulltext: 1.2,\n vector: 1.0,\n tokens: 0.8,\n },\n },\n presenterEnricher,\n })\n\n // Create search indexer with module configs\n const moduleConfigs = options?.moduleConfigs ?? []\n\n // Try to resolve fulltextIndexQueue for queue-based reindexing\n let fulltextQueue: Queue<FulltextIndexJobPayload> | undefined\n try {\n fulltextQueue = container.resolve<Queue<FulltextIndexJobPayload>>('fulltextIndexQueue')\n } catch {\n // Queue not available, queue-based fulltext reindex will be disabled\n }\n\n // Try to resolve vectorIndexQueue for queue-based vector reindexing\n let vectorQueue: Queue<VectorIndexJobPayload> | undefined\n try {\n vectorQueue = container.resolve<Queue<VectorIndexJobPayload>>('vectorIndexQueue')\n } catch {\n // Queue not available, queue-based vector reindex will be disabled\n }\n\n const searchIndexer = new SearchIndexer(searchService, moduleConfigs, {\n queryEngine,\n fulltextQueue,\n vectorQueue,\n })\n\n // Register in container\n container.register({\n searchService: asValue(searchService),\n searchStrategies: asValue(strategies),\n searchIndexer: asValue(searchIndexer),\n })\n}\n\n/**\n * Determine default strategy order based on available strategies.\n * Prefers fulltext > vector > tokens.\n */\nfunction determineDefaultStrategies(strategies: SearchStrategy[]): string[] {\n const available = new Set(strategies.map((s) => s.id))\n const defaults: string[] = []\n\n if (available.has('fulltext')) defaults.push('fulltext')\n if (available.has('vector')) defaults.push('vector')\n if (available.has('tokens')) defaults.push('tokens')\n\n return defaults.length > 0 ? defaults : ['tokens']\n}\n\n/**\n * Helper to add a custom strategy to an existing SearchService.\n *\n * @param container - DI container\n * @param strategy - Strategy to add\n */\nexport function addSearchStrategy(container: SearchContainer, strategy: SearchStrategy): void {\n const service = container.resolve<SearchService>('searchService')\n service.registerStrategy(strategy)\n\n const strategies = container.resolve<SearchStrategy[]>('searchStrategies')\n strategies.push(strategy)\n}\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -137,7 +137,7 @@ async function POST(req) {
137
137
  organizationId: orgId,
138
138
  recreateIndex: true,
139
139
  useQueue,
140
- onProgress: (progress) => {
140
+ onProgress: async (progress) => {
141
141
  searchDebug("search.reindex", "Progress", progress);
142
142
  }
143
143
  });
@@ -185,7 +185,7 @@ async function POST(req) {
185
185
  organizationId: orgId,
186
186
  recreateIndex: true,
187
187
  useQueue,
188
- onProgress: (progress) => {
188
+ onProgress: async (progress) => {
189
189
  searchDebug("search.reindex", "Progress", progress);
190
190
  }
191
191
  });
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../src/modules/search/api/reindex/route.ts"],
4
- "sourcesContent": ["import { NextResponse } from 'next/server'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport type { SearchStrategy } from '@open-mercato/shared/modules/search'\nimport type { SearchIndexer } from '@open-mercato/search/indexer'\nimport type { EntityId } from '@open-mercato/shared/modules/entities'\nimport { recordIndexerLog } from '@open-mercato/shared/lib/indexers/status-log'\nimport { recordIndexerError } from '@open-mercato/shared/lib/indexers/error-log'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport type { Knex } from 'knex'\nimport { searchDebug, searchError } from '../../../../lib/debug'\nimport {\n acquireReindexLock,\n clearReindexLock,\n getReindexLockStatus,\n} from '../../lib/reindex-lock'\nimport { reindexOpenApi } from '../openapi'\n\n/** Strategy with optional stats support */\ntype StrategyWithStats = SearchStrategy & {\n getIndexStats?: (tenantId: string) => Promise<Record<string, unknown> | null>\n clearIndex?: (tenantId: string) => Promise<void>\n recreateIndex?: (tenantId: string) => Promise<void>\n}\n\n/** Collect stats from all strategies that support it */\nasync function collectStrategyStats(\n strategies: StrategyWithStats[],\n tenantId: string\n): Promise<Record<string, Record<string, unknown> | null>> {\n const stats: Record<string, Record<string, unknown> | null> = {}\n for (const strategy of strategies) {\n if (typeof strategy.getIndexStats === 'function') {\n try {\n const isAvailable = await strategy.isAvailable()\n if (isAvailable) {\n stats[strategy.id] = await strategy.getIndexStats(tenantId)\n }\n } catch {\n // Skip strategy if stats collection fails\n }\n }\n }\n return stats\n}\n\nexport const metadata = {\n POST: { requireAuth: true, requireFeatures: ['search.reindex'] },\n}\n\ntype ReindexAction = 'clear' | 'recreate' | 'reindex'\n\nconst toJson = (payload: Record<string, unknown>, init?: ResponseInit) => NextResponse.json(payload, init)\n\nconst unauthorized = async () => {\n const { t } = await resolveTranslations()\n return NextResponse.json({ error: t('api.errors.unauthorized', 'Unauthorized') }, { status: 401 })\n}\n\nexport async function POST(req: Request) {\n const { t } = await resolveTranslations()\n const auth = await getAuthFromRequest(req)\n if (!auth?.tenantId) {\n return await unauthorized()\n }\n\n // Capture tenantId as non-null for TypeScript (we checked above)\n const tenantId = auth.tenantId\n\n let payload: { action?: ReindexAction; entityId?: string; useQueue?: boolean } = {}\n try {\n payload = await req.json()\n } catch {\n // Default to reindex\n }\n\n const action: ReindexAction =\n payload.action === 'clear' ? 'clear' :\n payload.action === 'recreate' ? 'recreate' : 'reindex'\n const entityId = typeof payload.entityId === 'string' ? payload.entityId : undefined\n // Use queue by default (requires queue workers to be running), can be disabled with useQueue: false\n const useQueue = payload.useQueue !== false\n\n const container = await createRequestContainer()\n const em = container.resolve('em') as EntityManager\n const knex = (em.getConnection() as unknown as { getKnex: () => Knex }).getKnex()\n\n // Check if another fulltext reindex operation is already in progress\n const existingLock = await getReindexLockStatus(knex, tenantId, { type: 'fulltext' })\n if (existingLock) {\n const startedAt = new Date(existingLock.startedAt)\n return NextResponse.json(\n {\n error: t('search.api.errors.reindexInProgress', 'A reindex operation is already in progress'),\n lock: {\n type: existingLock.type,\n action: existingLock.action,\n startedAt: existingLock.startedAt,\n elapsedMinutes: Math.round((Date.now() - startedAt.getTime()) / 60000),\n processedCount: existingLock.processedCount,\n totalCount: existingLock.totalCount,\n },\n },\n { status: 409 }\n )\n }\n\n // Acquire lock before starting the operation\n const { acquired: lockAcquired } = await acquireReindexLock(knex, {\n type: 'fulltext',\n action,\n tenantId: tenantId,\n organizationId: auth.orgId ?? null,\n })\n\n if (!lockAcquired) {\n return NextResponse.json(\n { error: t('search.api.errors.lockFailed', 'Failed to acquire reindex lock') },\n { status: 409 }\n )\n }\n\n try {\n // Get all search strategies\n const searchStrategies = (container.resolve('searchStrategies') as StrategyWithStats[] | undefined) ?? []\n\n // Find a strategy that supports index management (clear/recreate)\n const indexableStrategy = searchStrategies.find(\n (s) => typeof s.clearIndex === 'function' || typeof s.recreateIndex === 'function'\n )\n\n if (!indexableStrategy) {\n return toJson(\n { error: t('search.api.errors.noIndexableStrategy', 'No indexable search strategy is configured') },\n { status: 503 }\n )\n }\n\n // Check if strategy is available\n const isAvailable = await indexableStrategy.isAvailable()\n if (!isAvailable) {\n return toJson(\n { error: t('search.api.errors.strategyUnavailable', 'Search strategy is not available') },\n { status: 503 }\n )\n }\n\n // Perform the requested action\n if (action === 'reindex') {\n // Full reindex: recreate index and re-index all data\n const searchIndexer = container.resolve('searchIndexer') as SearchIndexer | undefined\n if (!searchIndexer) {\n return toJson(\n { error: t('search.api.errors.indexerUnavailable', 'Search indexer is not available') },\n { status: 503 }\n )\n }\n\n let result\n const orgId = typeof auth.orgId === 'string' ? auth.orgId : null\n\n // Debug: List enabled entities\n const enabledEntities = searchIndexer.listEnabledEntities()\n searchDebug('search.reindex', 'Starting reindex', {\n tenantId: tenantId,\n orgId,\n enabledEntities,\n entityId: entityId ?? 'all',\n useQueue,\n })\n\n // Log reindex started\n await recordIndexerLog(\n { em },\n {\n source: 'fulltext',\n handler: 'api:search.reindex',\n message: entityId\n ? `Starting Meilisearch reindex for ${entityId}`\n : `Starting Meilisearch reindex for all entities (${enabledEntities.join(', ')})`,\n entityType: entityId ?? null,\n tenantId: tenantId,\n organizationId: orgId,\n details: { enabledEntities, useQueue },\n },\n )\n\n if (entityId) {\n // Reindex specific entity\n result = await searchIndexer.reindexEntityToFulltext({\n entityId: entityId as EntityId,\n tenantId: tenantId,\n organizationId: orgId,\n recreateIndex: true,\n useQueue,\n onProgress: (progress) => {\n searchDebug('search.reindex', 'Progress', progress)\n // Note: Heartbeat is updated by workers during job processing, not during enqueueing\n },\n })\n searchDebug('search.reindex', 'Reindexed entity to Meilisearch', {\n entityId,\n tenantId: tenantId,\n recordsIndexed: result.recordsIndexed,\n jobsEnqueued: result.jobsEnqueued,\n errors: result.errors,\n })\n\n // Log to indexer status logs\n await recordIndexerLog(\n { em },\n {\n source: 'fulltext',\n handler: 'api:search.reindex',\n message: useQueue\n ? `Enqueued ${result.jobsEnqueued ?? 0} jobs for Meilisearch reindex of ${entityId}`\n : `Reindexed ${result.recordsIndexed} records to Meilisearch for ${entityId}`,\n entityType: entityId,\n tenantId: tenantId,\n organizationId: orgId,\n details: {\n recordsIndexed: result.recordsIndexed,\n jobsEnqueued: result.jobsEnqueued,\n useQueue,\n errors: result.errors.length > 0 ? result.errors : undefined,\n },\n },\n )\n\n // Log any batch errors to error logs\n for (const err of result.errors) {\n await recordIndexerError(\n { em },\n {\n source: 'fulltext',\n handler: 'api:search.reindex',\n error: new Error(err.error),\n entityType: err.entityId,\n tenantId: tenantId,\n organizationId: orgId,\n payload: { action, useQueue },\n },\n )\n }\n } else {\n // Reindex all entities\n result = await searchIndexer.reindexAllToFulltext({\n tenantId: tenantId,\n organizationId: orgId,\n recreateIndex: true,\n useQueue,\n onProgress: (progress) => {\n searchDebug('search.reindex', 'Progress', progress)\n // Note: Heartbeat is updated by workers during job processing, not during enqueueing\n },\n })\n searchDebug('search.reindex', 'Reindexed all entities to Meilisearch', {\n tenantId: tenantId,\n entitiesProcessed: result.entitiesProcessed,\n recordsIndexed: result.recordsIndexed,\n jobsEnqueued: result.jobsEnqueued,\n errors: result.errors,\n })\n\n // Log to indexer status logs\n await recordIndexerLog(\n { em },\n {\n source: 'fulltext',\n handler: 'api:search.reindex',\n message: useQueue\n ? `Enqueued ${result.jobsEnqueued ?? 0} jobs for Meilisearch reindex of all entities`\n : `Reindexed ${result.recordsIndexed} records to Meilisearch for ${result.entitiesProcessed} entities`,\n tenantId: tenantId,\n organizationId: orgId,\n details: {\n entitiesProcessed: result.entitiesProcessed,\n recordsIndexed: result.recordsIndexed,\n jobsEnqueued: result.jobsEnqueued,\n useQueue,\n errors: result.errors.length > 0 ? result.errors : undefined,\n },\n },\n )\n\n // Log any batch errors to error logs\n for (const err of result.errors) {\n await recordIndexerError(\n { em },\n {\n source: 'fulltext',\n handler: 'api:search.reindex',\n error: new Error(err.error),\n entityType: err.entityId,\n tenantId: tenantId,\n organizationId: orgId,\n payload: { action, useQueue },\n },\n )\n }\n }\n\n // Get updated stats from all strategies\n const stats = await collectStrategyStats(searchStrategies, tenantId)\n\n return toJson({\n ok: result.success,\n action,\n entityId: entityId ?? null,\n useQueue,\n result: {\n entitiesProcessed: result.entitiesProcessed,\n recordsIndexed: result.recordsIndexed,\n jobsEnqueued: result.jobsEnqueued ?? 0,\n errors: result.errors.length > 0 ? result.errors : undefined,\n },\n stats,\n })\n } else if (entityId) {\n // Purge specific entity\n await indexableStrategy.purge?.(entityId as EntityId, tenantId)\n searchDebug('search.reindex', 'Purged entity', { strategyId: indexableStrategy.id, entityId, tenantId: tenantId })\n\n await recordIndexerLog(\n { em },\n {\n source: 'fulltext',\n handler: 'api:search.reindex',\n message: `Purged entity ${entityId} from Meilisearch`,\n entityType: entityId,\n tenantId: tenantId,\n organizationId: auth.orgId ?? null,\n },\n )\n } else if (action === 'clear') {\n // Clear all documents but keep index\n if (indexableStrategy.clearIndex) {\n await indexableStrategy.clearIndex(tenantId)\n searchDebug('search.reindex', 'Cleared index', { strategyId: indexableStrategy.id, tenantId: tenantId })\n\n await recordIndexerLog(\n { em },\n {\n source: 'fulltext',\n handler: 'api:search.reindex',\n message: 'Cleared all documents from Meilisearch index',\n tenantId: tenantId,\n organizationId: auth.orgId ?? null,\n },\n )\n }\n } else {\n // Recreate the entire index\n if (indexableStrategy.recreateIndex) {\n await indexableStrategy.recreateIndex(tenantId)\n searchDebug('search.reindex', 'Recreated index', { strategyId: indexableStrategy.id, tenantId: tenantId })\n\n await recordIndexerLog(\n { em },\n {\n source: 'fulltext',\n handler: 'api:search.reindex',\n message: 'Recreated Meilisearch index',\n tenantId: tenantId,\n organizationId: auth.orgId ?? null,\n },\n )\n }\n }\n\n // Get updated stats from all strategies\n const stats = await collectStrategyStats(searchStrategies, tenantId)\n\n return toJson({\n ok: true,\n action,\n entityId: entityId ?? null,\n stats,\n })\n } catch (error: unknown) {\n // Log full error details server-side only\n searchError('search.reindex', 'Failed', {\n error: error instanceof Error ? error.message : error,\n stack: error instanceof Error ? error.stack : undefined,\n tenantId: tenantId,\n })\n\n // Record error to indexer error logs\n await recordIndexerError(\n { em },\n {\n source: 'fulltext',\n handler: 'api:search.reindex',\n error,\n entityType: entityId ?? null,\n tenantId: tenantId,\n organizationId: auth.orgId ?? null,\n payload: { action, entityId, useQueue },\n },\n )\n\n // Return generic message to client - don't expose internal error details\n return toJson(\n { error: t('search.api.errors.reindexFailed', 'Reindex operation failed. Please try again or contact support.') },\n { status: 500 }\n )\n } finally {\n // Only clear lock immediately if NOT using queue mode\n // When using queue mode, workers update heartbeat and stale detection handles cleanup\n if (!useQueue) {\n await clearReindexLock(knex, tenantId, 'fulltext', auth.orgId ?? null)\n }\n\n const disposable = container as unknown as { dispose?: () => Promise<void> }\n if (typeof disposable.dispose === 'function') {\n await disposable.dispose()\n }\n }\n}\n\nexport const openApi = reindexOpenApi\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["import { NextResponse } from 'next/server'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport type { SearchStrategy } from '@open-mercato/shared/modules/search'\nimport type { SearchIndexer } from '@open-mercato/search/indexer'\nimport type { EntityId } from '@open-mercato/shared/modules/entities'\nimport { recordIndexerLog } from '@open-mercato/shared/lib/indexers/status-log'\nimport { recordIndexerError } from '@open-mercato/shared/lib/indexers/error-log'\nimport type { EntityManager } from '@mikro-orm/postgresql'\nimport type { Knex } from 'knex'\nimport { searchDebug, searchError } from '../../../../lib/debug'\nimport {\n acquireReindexLock,\n clearReindexLock,\n getReindexLockStatus,\n} from '../../lib/reindex-lock'\nimport { reindexOpenApi } from '../openapi'\n\n/** Strategy with optional stats support */\ntype StrategyWithStats = SearchStrategy & {\n getIndexStats?: (tenantId: string) => Promise<Record<string, unknown> | null>\n clearIndex?: (tenantId: string) => Promise<void>\n recreateIndex?: (tenantId: string) => Promise<void>\n}\n\n/** Collect stats from all strategies that support it */\nasync function collectStrategyStats(\n strategies: StrategyWithStats[],\n tenantId: string\n): Promise<Record<string, Record<string, unknown> | null>> {\n const stats: Record<string, Record<string, unknown> | null> = {}\n for (const strategy of strategies) {\n if (typeof strategy.getIndexStats === 'function') {\n try {\n const isAvailable = await strategy.isAvailable()\n if (isAvailable) {\n stats[strategy.id] = await strategy.getIndexStats(tenantId)\n }\n } catch {\n // Skip strategy if stats collection fails\n }\n }\n }\n return stats\n}\n\nexport const metadata = {\n POST: { requireAuth: true, requireFeatures: ['search.reindex'] },\n}\n\ntype ReindexAction = 'clear' | 'recreate' | 'reindex'\n\nconst toJson = (payload: Record<string, unknown>, init?: ResponseInit) => NextResponse.json(payload, init)\n\nconst unauthorized = async () => {\n const { t } = await resolveTranslations()\n return NextResponse.json({ error: t('api.errors.unauthorized', 'Unauthorized') }, { status: 401 })\n}\n\nexport async function POST(req: Request) {\n const { t } = await resolveTranslations()\n const auth = await getAuthFromRequest(req)\n if (!auth?.tenantId) {\n return await unauthorized()\n }\n\n // Capture tenantId as non-null for TypeScript (we checked above)\n const tenantId = auth.tenantId\n\n let payload: { action?: ReindexAction; entityId?: string; useQueue?: boolean } = {}\n try {\n payload = await req.json()\n } catch {\n // Default to reindex\n }\n\n const action: ReindexAction =\n payload.action === 'clear' ? 'clear' :\n payload.action === 'recreate' ? 'recreate' : 'reindex'\n const entityId = typeof payload.entityId === 'string' ? payload.entityId : undefined\n // Use queue by default (requires queue workers to be running), can be disabled with useQueue: false\n const useQueue = payload.useQueue !== false\n\n const container = await createRequestContainer()\n const em = container.resolve('em') as EntityManager\n const knex = (em.getConnection() as unknown as { getKnex: () => Knex }).getKnex()\n\n // Check if another fulltext reindex operation is already in progress\n const existingLock = await getReindexLockStatus(knex, tenantId, { type: 'fulltext' })\n if (existingLock) {\n const startedAt = new Date(existingLock.startedAt)\n return NextResponse.json(\n {\n error: t('search.api.errors.reindexInProgress', 'A reindex operation is already in progress'),\n lock: {\n type: existingLock.type,\n action: existingLock.action,\n startedAt: existingLock.startedAt,\n elapsedMinutes: Math.round((Date.now() - startedAt.getTime()) / 60000),\n processedCount: existingLock.processedCount,\n totalCount: existingLock.totalCount,\n },\n },\n { status: 409 }\n )\n }\n\n // Acquire lock before starting the operation\n const { acquired: lockAcquired } = await acquireReindexLock(knex, {\n type: 'fulltext',\n action,\n tenantId: tenantId,\n organizationId: auth.orgId ?? null,\n })\n\n if (!lockAcquired) {\n return NextResponse.json(\n { error: t('search.api.errors.lockFailed', 'Failed to acquire reindex lock') },\n { status: 409 }\n )\n }\n\n try {\n // Get all search strategies\n const searchStrategies = (container.resolve('searchStrategies') as StrategyWithStats[] | undefined) ?? []\n\n // Find a strategy that supports index management (clear/recreate)\n const indexableStrategy = searchStrategies.find(\n (s) => typeof s.clearIndex === 'function' || typeof s.recreateIndex === 'function'\n )\n\n if (!indexableStrategy) {\n return toJson(\n { error: t('search.api.errors.noIndexableStrategy', 'No indexable search strategy is configured') },\n { status: 503 }\n )\n }\n\n // Check if strategy is available\n const isAvailable = await indexableStrategy.isAvailable()\n if (!isAvailable) {\n return toJson(\n { error: t('search.api.errors.strategyUnavailable', 'Search strategy is not available') },\n { status: 503 }\n )\n }\n\n // Perform the requested action\n if (action === 'reindex') {\n // Full reindex: recreate index and re-index all data\n const searchIndexer = container.resolve('searchIndexer') as SearchIndexer | undefined\n if (!searchIndexer) {\n return toJson(\n { error: t('search.api.errors.indexerUnavailable', 'Search indexer is not available') },\n { status: 503 }\n )\n }\n\n let result\n const orgId = typeof auth.orgId === 'string' ? auth.orgId : null\n\n // Debug: List enabled entities\n const enabledEntities = searchIndexer.listEnabledEntities()\n searchDebug('search.reindex', 'Starting reindex', {\n tenantId: tenantId,\n orgId,\n enabledEntities,\n entityId: entityId ?? 'all',\n useQueue,\n })\n\n // Log reindex started\n await recordIndexerLog(\n { em },\n {\n source: 'fulltext',\n handler: 'api:search.reindex',\n message: entityId\n ? `Starting Meilisearch reindex for ${entityId}`\n : `Starting Meilisearch reindex for all entities (${enabledEntities.join(', ')})`,\n entityType: entityId ?? null,\n tenantId: tenantId,\n organizationId: orgId,\n details: { enabledEntities, useQueue },\n },\n )\n\n if (entityId) {\n // Reindex specific entity\n result = await searchIndexer.reindexEntityToFulltext({\n entityId: entityId as EntityId,\n tenantId: tenantId,\n organizationId: orgId,\n recreateIndex: true,\n useQueue,\n onProgress: async (progress) => {\n searchDebug('search.reindex', 'Progress', progress)\n // Note: Heartbeat is updated by workers during job processing, not during enqueueing\n },\n })\n searchDebug('search.reindex', 'Reindexed entity to Meilisearch', {\n entityId,\n tenantId: tenantId,\n recordsIndexed: result.recordsIndexed,\n jobsEnqueued: result.jobsEnqueued,\n errors: result.errors,\n })\n\n // Log to indexer status logs\n await recordIndexerLog(\n { em },\n {\n source: 'fulltext',\n handler: 'api:search.reindex',\n message: useQueue\n ? `Enqueued ${result.jobsEnqueued ?? 0} jobs for Meilisearch reindex of ${entityId}`\n : `Reindexed ${result.recordsIndexed} records to Meilisearch for ${entityId}`,\n entityType: entityId,\n tenantId: tenantId,\n organizationId: orgId,\n details: {\n recordsIndexed: result.recordsIndexed,\n jobsEnqueued: result.jobsEnqueued,\n useQueue,\n errors: result.errors.length > 0 ? result.errors : undefined,\n },\n },\n )\n\n // Log any batch errors to error logs\n for (const err of result.errors) {\n await recordIndexerError(\n { em },\n {\n source: 'fulltext',\n handler: 'api:search.reindex',\n error: new Error(err.error),\n entityType: err.entityId,\n tenantId: tenantId,\n organizationId: orgId,\n payload: { action, useQueue },\n },\n )\n }\n } else {\n // Reindex all entities\n result = await searchIndexer.reindexAllToFulltext({\n tenantId: tenantId,\n organizationId: orgId,\n recreateIndex: true,\n useQueue,\n onProgress: async (progress) => {\n searchDebug('search.reindex', 'Progress', progress)\n // Note: Heartbeat is updated by workers during job processing, not during enqueueing\n },\n })\n searchDebug('search.reindex', 'Reindexed all entities to Meilisearch', {\n tenantId: tenantId,\n entitiesProcessed: result.entitiesProcessed,\n recordsIndexed: result.recordsIndexed,\n jobsEnqueued: result.jobsEnqueued,\n errors: result.errors,\n })\n\n // Log to indexer status logs\n await recordIndexerLog(\n { em },\n {\n source: 'fulltext',\n handler: 'api:search.reindex',\n message: useQueue\n ? `Enqueued ${result.jobsEnqueued ?? 0} jobs for Meilisearch reindex of all entities`\n : `Reindexed ${result.recordsIndexed} records to Meilisearch for ${result.entitiesProcessed} entities`,\n tenantId: tenantId,\n organizationId: orgId,\n details: {\n entitiesProcessed: result.entitiesProcessed,\n recordsIndexed: result.recordsIndexed,\n jobsEnqueued: result.jobsEnqueued,\n useQueue,\n errors: result.errors.length > 0 ? result.errors : undefined,\n },\n },\n )\n\n // Log any batch errors to error logs\n for (const err of result.errors) {\n await recordIndexerError(\n { em },\n {\n source: 'fulltext',\n handler: 'api:search.reindex',\n error: new Error(err.error),\n entityType: err.entityId,\n tenantId: tenantId,\n organizationId: orgId,\n payload: { action, useQueue },\n },\n )\n }\n }\n\n // Get updated stats from all strategies\n const stats = await collectStrategyStats(searchStrategies, tenantId)\n\n return toJson({\n ok: result.success,\n action,\n entityId: entityId ?? null,\n useQueue,\n result: {\n entitiesProcessed: result.entitiesProcessed,\n recordsIndexed: result.recordsIndexed,\n jobsEnqueued: result.jobsEnqueued ?? 0,\n errors: result.errors.length > 0 ? result.errors : undefined,\n },\n stats,\n })\n } else if (entityId) {\n // Purge specific entity\n await indexableStrategy.purge?.(entityId as EntityId, tenantId)\n searchDebug('search.reindex', 'Purged entity', { strategyId: indexableStrategy.id, entityId, tenantId: tenantId })\n\n await recordIndexerLog(\n { em },\n {\n source: 'fulltext',\n handler: 'api:search.reindex',\n message: `Purged entity ${entityId} from Meilisearch`,\n entityType: entityId,\n tenantId: tenantId,\n organizationId: auth.orgId ?? null,\n },\n )\n } else if (action === 'clear') {\n // Clear all documents but keep index\n if (indexableStrategy.clearIndex) {\n await indexableStrategy.clearIndex(tenantId)\n searchDebug('search.reindex', 'Cleared index', { strategyId: indexableStrategy.id, tenantId: tenantId })\n\n await recordIndexerLog(\n { em },\n {\n source: 'fulltext',\n handler: 'api:search.reindex',\n message: 'Cleared all documents from Meilisearch index',\n tenantId: tenantId,\n organizationId: auth.orgId ?? null,\n },\n )\n }\n } else {\n // Recreate the entire index\n if (indexableStrategy.recreateIndex) {\n await indexableStrategy.recreateIndex(tenantId)\n searchDebug('search.reindex', 'Recreated index', { strategyId: indexableStrategy.id, tenantId: tenantId })\n\n await recordIndexerLog(\n { em },\n {\n source: 'fulltext',\n handler: 'api:search.reindex',\n message: 'Recreated Meilisearch index',\n tenantId: tenantId,\n organizationId: auth.orgId ?? null,\n },\n )\n }\n }\n\n // Get updated stats from all strategies\n const stats = await collectStrategyStats(searchStrategies, tenantId)\n\n return toJson({\n ok: true,\n action,\n entityId: entityId ?? null,\n stats,\n })\n } catch (error: unknown) {\n // Log full error details server-side only\n searchError('search.reindex', 'Failed', {\n error: error instanceof Error ? error.message : error,\n stack: error instanceof Error ? error.stack : undefined,\n tenantId: tenantId,\n })\n\n // Record error to indexer error logs\n await recordIndexerError(\n { em },\n {\n source: 'fulltext',\n handler: 'api:search.reindex',\n error,\n entityType: entityId ?? null,\n tenantId: tenantId,\n organizationId: auth.orgId ?? null,\n payload: { action, entityId, useQueue },\n },\n )\n\n // Return generic message to client - don't expose internal error details\n return toJson(\n { error: t('search.api.errors.reindexFailed', 'Reindex operation failed. Please try again or contact support.') },\n { status: 500 }\n )\n } finally {\n // Only clear lock immediately if NOT using queue mode\n // When using queue mode, workers update heartbeat and stale detection handles cleanup\n if (!useQueue) {\n await clearReindexLock(knex, tenantId, 'fulltext', auth.orgId ?? null)\n }\n\n const disposable = container as unknown as { dispose?: () => Promise<void> }\n if (typeof disposable.dispose === 'function') {\n await disposable.dispose()\n }\n }\n}\n\nexport const openApi = reindexOpenApi\n"],
5
+ "mappings": "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",
6
6
  "names": ["stats"]
7
7
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../src/modules/search/api/search/route.ts"],
4
- "sourcesContent": ["import { NextResponse } from 'next/server'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { resolveOrganizationScopeForRequest } from '@open-mercato/core/modules/directory/utils/organizationScope'\nimport type { SearchService } from '@open-mercato/search'\nimport type { SearchStrategyId } from '@open-mercato/shared/modules/search'\nimport type { EmbeddingService } from '../../../../vector'\nimport { resolveEmbeddingConfig } from '../../lib/embedding-config'\nimport { searchError } from '../../../../lib/debug'\nimport { searchOpenApi } from '../openapi'\n\nexport const metadata = {\n GET: { requireAuth: true, requireFeatures: ['search.view'] },\n}\n\nfunction parseLimit(value: string | null): number {\n if (!value) return 50\n const parsed = Number.parseInt(value, 10)\n if (Number.isNaN(parsed) || parsed <= 0) return 50\n return Math.min(parsed, 100)\n}\n\nfunction parseStrategies(value: string | null): SearchStrategyId[] | undefined {\n if (!value) return undefined\n const strategies = value.split(',').map((s) => s.trim()).filter(Boolean) as SearchStrategyId[]\n return strategies.length > 0 ? strategies : undefined\n}\n\nfunction parseEntityTypes(value: string | null): string[] | undefined {\n if (!value) return undefined\n const entityTypes = value.split(',').map((s) => s.trim()).filter(Boolean)\n return entityTypes.length > 0 ? entityTypes : undefined\n}\n\nexport async function GET(req: Request) {\n const { t } = await resolveTranslations()\n const url = new URL(req.url)\n const query = (url.searchParams.get('q') || '').trim()\n const limit = parseLimit(url.searchParams.get('limit'))\n const strategies = parseStrategies(url.searchParams.get('strategies'))\n const entityTypes = parseEntityTypes(url.searchParams.get('entityTypes'))\n\n if (!query) {\n return NextResponse.json(\n { error: t('search.api.errors.missingQuery', 'Missing query') },\n { status: 400 }\n )\n }\n\n const auth = await getAuthFromRequest(req)\n if (!auth?.tenantId) {\n return NextResponse.json(\n { error: t('api.errors.unauthorized', 'Unauthorized') },\n { status: 401 }\n )\n }\n\n const container = await createRequestContainer()\n try {\n const searchService = container.resolve('searchService') as SearchService | undefined\n if (!searchService) {\n return NextResponse.json(\n { error: t('search.api.errors.serviceUnavailable', 'Search service unavailable') },\n { status: 503 }\n )\n }\n\n // Load embedding config for vector strategy (same as Vector Search playground)\n try {\n const embeddingConfig = await resolveEmbeddingConfig(container, { defaultValue: null })\n if (embeddingConfig) {\n const embeddingService = container.resolve<EmbeddingService>('vectorEmbeddingService')\n embeddingService.updateConfig(embeddingConfig)\n }\n } catch {\n // Embedding config not available, vector strategy may not work\n }\n\n const startTime = Date.now()\n\n const scope = await resolveOrganizationScopeForRequest({ container, auth, request: req })\n if (Array.isArray(scope.filterIds) && scope.filterIds.length === 0) {\n return NextResponse.json({\n results: [],\n strategiesUsed: [],\n timing: 0,\n query,\n limit,\n })\n }\n\n const organizationId =\n typeof scope.selectedId === 'string' && scope.selectedId.trim().length > 0 ? scope.selectedId.trim() : undefined\n const searchOptions = {\n tenantId: auth.tenantId,\n organizationId,\n limit,\n strategies,\n entityTypes,\n }\n\n const results = await searchService.search(query, searchOptions)\n\n const timing = Date.now() - startTime\n\n // Collect unique strategies that returned results\n const strategiesUsed = [...new Set(results.map((r) => r.source))]\n\n return NextResponse.json({\n results,\n strategiesUsed,\n timing,\n query,\n limit,\n })\n } catch (error: unknown) {\n // Log full error details server-side only\n searchError('search.api.search', 'failed', {\n error: error instanceof Error ? error.message : error,\n stack: error instanceof Error ? error.stack : undefined,\n })\n // Return generic message to client - don't expose internal error details\n return NextResponse.json(\n { error: t('search.api.errors.searchFailed', 'Search failed. Please try again.') },\n { status: 500 }\n )\n } finally {\n const disposable = container as unknown as { dispose?: () => Promise<void> }\n if (typeof disposable.dispose === 'function') {\n await disposable.dispose()\n }\n }\n}\n\nexport const openApi = searchOpenApi\n"],
4
+ "sourcesContent": ["import { NextResponse } from 'next/server'\nimport { createRequestContainer } from '@open-mercato/shared/lib/di/container'\nimport { getAuthFromRequest } from '@open-mercato/shared/lib/auth/server'\nimport { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'\nimport { resolveOrganizationScopeForRequest } from '@open-mercato/core/modules/directory/utils/organizationScope'\nimport type { SearchService } from '@open-mercato/search'\nimport type { SearchStrategyId } from '@open-mercato/shared/modules/search'\nimport type { EmbeddingService } from '../../../../vector'\nimport { resolveEmbeddingConfig } from '../../lib/embedding-config'\nimport { searchError } from '../../../../lib/debug'\nimport { searchOpenApi } from '../openapi'\n\nexport const metadata = {\n GET: { requireAuth: true, requireFeatures: ['search.view'] },\n}\n\nfunction parseLimit(value: string | null): number {\n if (!value) return 50\n const parsed = Number.parseInt(value, 10)\n if (Number.isNaN(parsed) || parsed <= 0) return 50\n return Math.min(parsed, 100)\n}\n\nfunction parseStrategies(value: string | null): SearchStrategyId[] | undefined {\n if (!value) return undefined\n const strategies = value.split(',').map((s) => s.trim()).filter(Boolean)\n return strategies.length > 0 ? strategies : undefined\n}\n\nfunction parseEntityTypes(value: string | null): string[] | undefined {\n if (!value) return undefined\n const entityTypes = value.split(',').map((s) => s.trim()).filter(Boolean)\n return entityTypes.length > 0 ? entityTypes : undefined\n}\n\nexport async function GET(req: Request) {\n const { t } = await resolveTranslations()\n const url = new URL(req.url)\n const query = (url.searchParams.get('q') || '').trim()\n const limit = parseLimit(url.searchParams.get('limit'))\n const strategies = parseStrategies(url.searchParams.get('strategies'))\n const entityTypes = parseEntityTypes(url.searchParams.get('entityTypes'))\n\n if (!query) {\n return NextResponse.json(\n { error: t('search.api.errors.missingQuery', 'Missing query') },\n { status: 400 }\n )\n }\n\n const auth = await getAuthFromRequest(req)\n if (!auth?.tenantId) {\n return NextResponse.json(\n { error: t('api.errors.unauthorized', 'Unauthorized') },\n { status: 401 }\n )\n }\n\n const container = await createRequestContainer()\n try {\n const searchService = container.resolve('searchService') as SearchService | undefined\n if (!searchService) {\n return NextResponse.json(\n { error: t('search.api.errors.serviceUnavailable', 'Search service unavailable') },\n { status: 503 }\n )\n }\n\n // Load embedding config for vector strategy (same as Vector Search playground)\n try {\n const embeddingConfig = await resolveEmbeddingConfig(container, { defaultValue: null })\n if (embeddingConfig) {\n const embeddingService = container.resolve<EmbeddingService>('vectorEmbeddingService')\n embeddingService.updateConfig(embeddingConfig)\n }\n } catch {\n // Embedding config not available, vector strategy may not work\n }\n\n const startTime = Date.now()\n\n const scope = await resolveOrganizationScopeForRequest({ container, auth, request: req })\n if (Array.isArray(scope.filterIds) && scope.filterIds.length === 0) {\n return NextResponse.json({\n results: [],\n strategiesUsed: [],\n timing: 0,\n query,\n limit,\n })\n }\n\n const organizationId =\n typeof scope.selectedId === 'string' && scope.selectedId.trim().length > 0 ? scope.selectedId.trim() : undefined\n const searchOptions = {\n tenantId: auth.tenantId,\n organizationId,\n limit,\n strategies,\n entityTypes,\n }\n\n const results = await searchService.search(query, searchOptions)\n\n const timing = Date.now() - startTime\n\n // Collect unique strategies that returned results\n const strategiesUsed = [...new Set(results.map((r) => r.source))]\n\n return NextResponse.json({\n results,\n strategiesUsed,\n timing,\n query,\n limit,\n })\n } catch (error: unknown) {\n // Log full error details server-side only\n searchError('search.api.search', 'failed', {\n error: error instanceof Error ? error.message : error,\n stack: error instanceof Error ? error.stack : undefined,\n })\n // Return generic message to client - don't expose internal error details\n return NextResponse.json(\n { error: t('search.api.errors.searchFailed', 'Search failed. Please try again.') },\n { status: 500 }\n )\n } finally {\n const disposable = container as unknown as { dispose?: () => Promise<void> }\n if (typeof disposable.dispose === 'function') {\n await disposable.dispose()\n }\n }\n}\n\nexport const openApi = searchOpenApi\n"],
5
5
  "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -358,7 +358,7 @@ function VectorSearchSection({
358
358
  ] }),
359
359
  /* @__PURE__ */ jsx("div", { className: `flex h-5 w-5 items-center justify-center rounded-full flex-shrink-0 ${isSelected ? "bg-primary text-primary-foreground" : isConfigured ? "bg-emerald-100 text-emerald-600 dark:bg-emerald-900/40 dark:text-emerald-400" : "bg-muted text-muted-foreground"}`, children: isSelected ? /* @__PURE__ */ jsx("svg", { className: "h-3 w-3", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", strokeWidth: 3, children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M5 13l4 4L19 7" }) }) : isConfigured ? /* @__PURE__ */ jsx("svg", { className: "h-3 w-3", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", strokeWidth: 2, children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M5 13l4 4L19 7" }) }) : /* @__PURE__ */ jsx("svg", { className: "h-3 w-3", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", strokeWidth: 2, children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", d: "M12 4v16m8-8H4" }) }) })
360
360
  ] }),
361
- isSelected && isConfigured && /* @__PURE__ */ jsxs("div", { className: "mt-3 pt-3 border-t border-border space-y-2", onClick: (e) => e.stopPropagation(), onKeyDown: (e) => e.stopPropagation(), role: "presentation", children: [
361
+ isSelected && isConfigured && /* @__PURE__ */ jsxs("div", { className: "mt-3 pt-3 border-t border-border space-y-2", onClick: (e) => e.stopPropagation(), children: [
362
362
  /* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
363
363
  /* @__PURE__ */ jsx(Label, { htmlFor: `model-${providerId}`, className: "text-xs font-medium", children: t("search.settings.model.label", "Model") }),
364
364
  /* @__PURE__ */ jsxs(
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../../../../../src/modules/search/frontend/components/sections/VectorSearchSection.tsx"],
4
- "sourcesContent": ["'use client'\n\nimport * as React from 'react'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { readApiResultOrThrow } from '@open-mercato/ui/backend/utils/apiCall'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { Label } from '@open-mercato/ui/primitives/label'\nimport { Spinner } from '@open-mercato/ui/primitives/spinner'\nimport { Tabs, TabsList, TabsTrigger, TabsContent } from '@open-mercato/ui/primitives/tabs'\n\n// Types\ntype EmbeddingProviderId = 'openai' | 'google' | 'mistral' | 'cohere' | 'bedrock' | 'ollama'\n\ntype EmbeddingProviderConfig = {\n providerId: EmbeddingProviderId\n model: string\n dimension: number\n outputDimensionality?: number\n baseUrl?: string\n updatedAt: string\n}\n\ntype EmbeddingModelInfo = {\n id: string\n name: string\n dimension: number\n configurableDimension?: boolean\n minDimension?: number\n maxDimension?: number\n}\n\ntype EmbeddingProviderInfo = {\n name: string\n envKeyRequired: string\n defaultModel: string\n models: EmbeddingModelInfo[]\n}\n\ntype EmbeddingSettings = {\n openaiConfigured: boolean\n autoIndexingEnabled: boolean\n autoIndexingLocked: boolean\n lockReason: string | null\n embeddingConfig: EmbeddingProviderConfig | null\n configuredProviders: EmbeddingProviderId[]\n indexedDimension: number | null\n reindexRequired: boolean\n documentCount: number | null\n}\n\ntype EmbeddingSettingsResponse = {\n settings?: EmbeddingSettings\n error?: string\n}\n\ntype VectorDriverId = 'pgvector' | 'qdrant' | 'chromadb'\n\ntype VectorDriverEnvVar = {\n name: string\n set: boolean\n hint: string\n}\n\ntype VectorDriverStatus = {\n id: VectorDriverId\n name: string\n configured: boolean\n implemented: boolean\n envVars: VectorDriverEnvVar[]\n}\n\ntype VectorStoreConfigResponse = {\n currentDriver: VectorDriverId\n configured: boolean\n drivers: VectorDriverStatus[]\n}\n\ntype ReindexLock = {\n type: 'fulltext' | 'vector'\n action: string\n startedAt: string\n elapsedMinutes: number\n}\n\ntype ActivityLog = {\n id: string\n source: string\n handler: string\n level: 'info' | 'error' | 'warn'\n entityType: string | null\n recordId: string | null\n message: string\n details: unknown\n occurredAt: string\n}\n\nconst EMBEDDING_PROVIDERS: Record<EmbeddingProviderId, EmbeddingProviderInfo> = {\n openai: {\n name: 'OpenAI',\n envKeyRequired: 'OPENAI_API_KEY',\n defaultModel: 'text-embedding-3-small',\n models: [\n { id: 'text-embedding-3-small', name: 'text-embedding-3-small', dimension: 1536 },\n { id: 'text-embedding-3-large', name: 'text-embedding-3-large', dimension: 3072, configurableDimension: true, minDimension: 256, maxDimension: 3072 },\n { id: 'text-embedding-ada-002', name: 'text-embedding-ada-002', dimension: 1536 },\n ],\n },\n google: {\n name: 'Google Generative AI',\n envKeyRequired: 'GOOGLE_GENERATIVE_AI_API_KEY',\n defaultModel: 'text-embedding-004',\n models: [\n { id: 'text-embedding-004', name: 'text-embedding-004', dimension: 768, configurableDimension: true, minDimension: 1, maxDimension: 768 },\n { id: 'embedding-001', name: 'embedding-001', dimension: 768 },\n ],\n },\n mistral: {\n name: 'Mistral',\n envKeyRequired: 'MISTRAL_API_KEY',\n defaultModel: 'mistral-embed',\n models: [\n { id: 'mistral-embed', name: 'mistral-embed', dimension: 1024 },\n ],\n },\n cohere: {\n name: 'Cohere',\n envKeyRequired: 'COHERE_API_KEY',\n defaultModel: 'embed-english-v3.0',\n models: [\n { id: 'embed-english-v3.0', name: 'embed-english-v3.0', dimension: 1024 },\n { id: 'embed-multilingual-v3.0', name: 'embed-multilingual-v3.0', dimension: 1024 },\n { id: 'embed-english-light-v3.0', name: 'embed-english-light-v3.0', dimension: 384 },\n { id: 'embed-multilingual-light-v3.0', name: 'embed-multilingual-light-v3.0', dimension: 384 },\n ],\n },\n bedrock: {\n name: 'Amazon Bedrock',\n envKeyRequired: 'AWS_ACCESS_KEY_ID',\n defaultModel: 'amazon.titan-embed-text-v2:0',\n models: [\n { id: 'amazon.titan-embed-text-v2:0', name: 'Titan Embed Text v2', dimension: 1024, configurableDimension: true, minDimension: 256, maxDimension: 1024 },\n { id: 'amazon.titan-embed-text-v1', name: 'Titan Embed Text v1', dimension: 1536 },\n { id: 'cohere.embed-english-v3', name: 'Cohere Embed English v3', dimension: 1024 },\n { id: 'cohere.embed-multilingual-v3', name: 'Cohere Embed Multilingual v3', dimension: 1024 },\n ],\n },\n ollama: {\n name: 'Ollama (Local)',\n envKeyRequired: 'OLLAMA_BASE_URL',\n defaultModel: 'nomic-embed-text',\n models: [\n { id: 'nomic-embed-text', name: 'nomic-embed-text', dimension: 768 },\n { id: 'mxbai-embed-large', name: 'mxbai-embed-large', dimension: 1024 },\n { id: 'all-minilm', name: 'all-minilm', dimension: 384 },\n { id: 'snowflake-arctic-embed', name: 'snowflake-arctic-embed', dimension: 1024 },\n ],\n },\n}\n\nexport type VectorSearchSectionProps = {\n embeddingSettings: EmbeddingSettings | null\n embeddingLoading: boolean\n vectorStoreConfig: VectorStoreConfigResponse | null\n vectorStoreConfigLoading: boolean\n vectorReindexLock: ReindexLock | null\n onEmbeddingSettingsUpdate: (settings: EmbeddingSettings) => void\n onRefreshEmbeddings: () => Promise<void>\n}\n\nexport function VectorSearchSection({\n embeddingSettings,\n embeddingLoading,\n vectorStoreConfig,\n vectorStoreConfigLoading,\n vectorReindexLock,\n onEmbeddingSettingsUpdate,\n onRefreshEmbeddings,\n}: VectorSearchSectionProps) {\n const t = useT()\n const [embeddingSaving, setEmbeddingSaving] = React.useState(false)\n const autoIndexingPreviousRef = React.useRef<boolean>(true)\n\n // Staged embedding selection\n const [selectedProvider, setSelectedProvider] = React.useState<EmbeddingProviderId | null>(null)\n const [selectedModel, setSelectedModel] = React.useState<string | null>(null)\n const [customModelName, setCustomModelName] = React.useState<string>('')\n const [customDimension, setCustomDimension] = React.useState<number>(768)\n\n const [pendingEmbeddingConfig, setPendingEmbeddingConfig] = React.useState<EmbeddingProviderConfig | null>(null)\n const [showEmbeddingConfirmDialog, setShowEmbeddingConfirmDialog] = React.useState(false)\n\n // Vector reindex state\n const [vectorReindexing, setVectorReindexing] = React.useState(false)\n const [showVectorReindexDialog, setShowVectorReindexDialog] = React.useState(false)\n\n // Activity logs state\n const [activityLogs, setActivityLogs] = React.useState<ActivityLog[]>([])\n const [activityLoading, setActivityLoading] = React.useState(true)\n\n // Fetch activity logs\n const fetchActivityLogs = React.useCallback(async () => {\n setActivityLoading(true)\n try {\n const response = await fetch('/api/query_index/status')\n if (response.ok) {\n const body = await response.json() as { logs?: ActivityLog[]; errors?: ActivityLog[] }\n const allLogs: ActivityLog[] = []\n if (body.logs) {\n allLogs.push(...body.logs)\n }\n if (body.errors) {\n allLogs.push(...body.errors.map(err => ({ ...err, level: 'error' as const })))\n }\n // Filter for vector-related logs\n const vectorLogs = allLogs.filter(log => {\n const lowerSource = log.source?.toLowerCase() ?? ''\n const lowerMessage = log.message?.toLowerCase() ?? ''\n const lowerHandler = log.handler?.toLowerCase() ?? ''\n return lowerSource.includes('vector') || lowerMessage.includes('vector') ||\n lowerMessage.includes('embedding') || lowerHandler.includes('vector')\n })\n vectorLogs.sort((a, b) => new Date(b.occurredAt).getTime() - new Date(a.occurredAt).getTime())\n setActivityLogs(vectorLogs.slice(0, 50))\n }\n } catch {\n // Silently fail\n } finally {\n setActivityLoading(false)\n }\n }, [])\n\n React.useEffect(() => {\n fetchActivityLogs()\n }, [fetchActivityLogs])\n\n // Poll for activity when reindexing\n React.useEffect(() => {\n if (vectorReindexLock || vectorReindexing) {\n const interval = setInterval(fetchActivityLogs, 5000)\n return () => clearInterval(interval)\n }\n }, [vectorReindexLock, vectorReindexing, fetchActivityLogs])\n\n // Update auto-indexing\n const updateAutoIndexing = React.useCallback(async (nextValue: boolean) => {\n autoIndexingPreviousRef.current = embeddingSettings?.autoIndexingEnabled ?? true\n if (embeddingSettings) {\n onEmbeddingSettingsUpdate({ ...embeddingSettings, autoIndexingEnabled: nextValue })\n }\n setEmbeddingSaving(true)\n try {\n const body = await readApiResultOrThrow<EmbeddingSettingsResponse>(\n '/api/search/embeddings',\n {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ autoIndexingEnabled: nextValue }),\n },\n { errorMessage: t('search.settings.errors.saveFailed', 'Failed to save settings'), allowNullResult: true },\n )\n if (body?.settings) {\n onEmbeddingSettingsUpdate(body.settings)\n autoIndexingPreviousRef.current = body.settings.autoIndexingEnabled\n }\n flash(t('search.settings.messages.saved', 'Settings saved'), 'success')\n } catch {\n if (embeddingSettings) {\n onEmbeddingSettingsUpdate({ ...embeddingSettings, autoIndexingEnabled: autoIndexingPreviousRef.current })\n }\n } finally {\n setEmbeddingSaving(false)\n }\n }, [embeddingSettings, onEmbeddingSettingsUpdate, t])\n\n // Provider handlers\n const handleProviderChange = (providerId: EmbeddingProviderId) => {\n setSelectedProvider(providerId)\n setSelectedModel(null)\n setCustomModelName('')\n setCustomDimension(768)\n }\n\n const handleModelChange = (modelId: string) => {\n setSelectedModel(modelId)\n }\n\n const handleApplyEmbeddingChanges = () => {\n const newProviderId = selectedProvider ?? embeddingSettings?.embeddingConfig?.providerId ?? 'openai'\n const newProviderInfo = EMBEDDING_PROVIDERS[newProviderId]\n const newModelId = selectedModel ?? (selectedProvider ? newProviderInfo.defaultModel : embeddingSettings?.embeddingConfig?.model ?? newProviderInfo.defaultModel)\n\n let modelName: string\n let dimension: number\n\n if (newModelId === 'custom') {\n modelName = customModelName.trim()\n dimension = customDimension\n if (!modelName) {\n flash(t('search.settings.errors.modelRequired', 'Please enter a model name'), 'error')\n return\n }\n if (dimension <= 0) {\n flash(t('search.settings.errors.dimensionRequired', 'Please enter a valid dimension'), 'error')\n return\n }\n } else {\n const newModel = newProviderInfo.models.find((m) => m.id === newModelId) ?? newProviderInfo.models[0]\n modelName = newModel.id\n dimension = newModel.dimension\n }\n\n const newConfig: EmbeddingProviderConfig = {\n providerId: newProviderId,\n model: modelName,\n dimension,\n updatedAt: new Date().toISOString(),\n }\n\n if (embeddingSettings?.indexedDimension || embeddingSettings?.embeddingConfig) {\n setPendingEmbeddingConfig(newConfig)\n setShowEmbeddingConfirmDialog(true)\n } else {\n applyEmbeddingConfig(newConfig)\n }\n }\n\n const handleCancelEmbeddingSelection = () => {\n setSelectedProvider(null)\n setSelectedModel(null)\n setCustomModelName('')\n setCustomDimension(768)\n }\n\n const applyEmbeddingConfig = async (config: EmbeddingProviderConfig) => {\n setEmbeddingSaving(true)\n setShowEmbeddingConfirmDialog(false)\n setPendingEmbeddingConfig(null)\n\n try {\n await readApiResultOrThrow<EmbeddingSettingsResponse>(\n '/api/search/embeddings',\n {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ embeddingConfig: config }),\n },\n { errorMessage: t('search.settings.errors.saveFailed', 'Failed to save settings'), allowNullResult: true },\n )\n setSelectedProvider(null)\n setSelectedModel(null)\n flash(t('search.settings.messages.providerSaved', 'Embedding provider saved'), 'success')\n await onRefreshEmbeddings()\n } catch {\n // Error handled by readApiResultOrThrow\n } finally {\n setEmbeddingSaving(false)\n }\n }\n\n const handleEmbeddingConfirmChange = () => {\n if (pendingEmbeddingConfig) {\n applyEmbeddingConfig(pendingEmbeddingConfig)\n }\n }\n\n const handleEmbeddingCancelChange = () => {\n setShowEmbeddingConfirmDialog(false)\n setPendingEmbeddingConfig(null)\n }\n\n // Vector reindex handlers\n const handleVectorReindexClick = () => {\n setShowVectorReindexDialog(true)\n }\n\n const handleVectorReindexConfirm = async () => {\n setShowVectorReindexDialog(false)\n setVectorReindexing(true)\n try {\n await readApiResultOrThrow<{ ok: boolean }>(\n '/api/search/embeddings/reindex',\n {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ purgeFirst: true }),\n },\n { errorMessage: t('search.settings.errors.reindexFailed', 'Reindex failed'), allowNullResult: true },\n )\n flash(t('search.settings.messages.reindexStarted', 'Reindex started'), 'success')\n await fetchActivityLogs()\n } catch {\n // Error handled by readApiResultOrThrow\n } finally {\n setVectorReindexing(false)\n }\n }\n\n const handleVectorReindexCancel = () => {\n setShowVectorReindexDialog(false)\n }\n\n // Computed values\n const savedProvider = embeddingSettings?.embeddingConfig?.providerId ?? 'openai'\n const savedProviderInfo = EMBEDDING_PROVIDERS[savedProvider]\n const savedModel = embeddingSettings?.embeddingConfig?.model ?? savedProviderInfo.defaultModel\n const savedDimension = embeddingSettings?.embeddingConfig?.dimension ?? savedProviderInfo.models[0]?.dimension ?? 768\n\n const savedModelIsPredefined = savedProviderInfo.models.some((m) => m.id === savedModel)\n const savedCustomModel = !savedModelIsPredefined && savedModel ? { id: savedModel, name: savedModel, dimension: savedDimension } : null\n\n const displayProvider = selectedProvider ?? savedProvider\n const displayProviderInfo = EMBEDDING_PROVIDERS[displayProvider]\n const displayModel = selectedModel ?? (selectedProvider ? displayProviderInfo.defaultModel : savedModel)\n const isCustomModel = displayModel === 'custom'\n\n const displayModelIsSavedCustom = !isCustomModel && displayProvider === savedProvider && savedCustomModel && displayModel === savedCustomModel.id\n\n const displayModelInfo = isCustomModel\n ? null\n : displayModelIsSavedCustom\n ? savedCustomModel\n : displayProviderInfo.models.find((m) => m.id === displayModel) ?? displayProviderInfo.models[0]\n const displayDimension = isCustomModel ? customDimension : (displayModelInfo?.dimension ?? 768)\n\n const hasUnsavedEmbeddingChanges = (selectedProvider !== null && selectedProvider !== savedProvider) ||\n (selectedModel !== null && selectedModel !== savedModel) ||\n (selectedProvider !== null && selectedModel === null && displayProviderInfo.defaultModel !== savedModel) ||\n (isCustomModel && (customModelName.trim() !== '' || customDimension !== 768))\n\n const isEmbeddingConfigured = embeddingSettings?.configuredProviders?.includes(savedProvider)\n const providerOptions: EmbeddingProviderId[] = ['openai', 'google', 'mistral', 'cohere', 'bedrock', 'ollama']\n\n const autoIndexingChecked = embeddingSettings ? embeddingSettings.autoIndexingEnabled : true\n const autoIndexingDisabled = embeddingLoading || embeddingSaving || Boolean(embeddingSettings?.autoIndexingLocked)\n\n return (\n <div className=\"rounded-lg border border-border bg-card p-5 shadow-sm\">\n <h2 className=\"text-lg font-semibold mb-2\">\n {t('search.settings.vector.sectionTitle', 'Vector Search')}\n </h2>\n <p className=\"text-sm text-muted-foreground mb-4\">\n {t('search.settings.vector.sectionDescription', 'AI-powered semantic search using embeddings.')}\n </p>\n\n <Tabs defaultValue=\"configuration\">\n <TabsList className=\"mb-4\">\n <TabsTrigger value=\"configuration\">\n {t('search.settings.tabs.configuration', 'Configuration')}\n </TabsTrigger>\n <TabsTrigger value=\"index\">\n {t('search.settings.tabs.indexManagement', 'Index Management')}\n </TabsTrigger>\n <TabsTrigger value=\"activity\">\n {t('search.settings.tabs.activity', 'Activity')}\n </TabsTrigger>\n </TabsList>\n\n {/* Configuration Tab */}\n <TabsContent value=\"configuration\">\n {(embeddingLoading || vectorStoreConfigLoading) ? (\n <div className=\"flex items-center gap-2 text-muted-foreground\">\n <Spinner size=\"sm\" />\n <span>{t('search.settings.loadingLabel', 'Loading settings...')}</span>\n </div>\n ) : (\n <div className=\"space-y-4\">\n {/* Vector Store Driver Status */}\n <div>\n <h3 className=\"text-sm font-semibold mb-2\">{t('search.settings.vector.store', 'Vector Store')}</h3>\n <div className=\"grid gap-2 sm:grid-cols-3\">\n {vectorStoreConfig?.drivers.map((driver) => {\n const isCurrent = driver.id === vectorStoreConfig.currentDriver\n const isReady = driver.configured && driver.implemented\n return (\n <div\n key={driver.id}\n className={`flex items-start gap-3 p-3 rounded-md border ${\n isCurrent && isReady\n ? 'border-emerald-200 bg-emerald-50 dark:border-emerald-800 dark:bg-emerald-900/20'\n : !driver.implemented\n ? 'border-border bg-muted/20 opacity-60'\n : 'border-border bg-muted/30'\n }`}\n >\n <div className={`flex h-8 w-8 items-center justify-center rounded-full flex-shrink-0 ${\n isCurrent && isReady\n ? 'bg-emerald-100 text-emerald-600 dark:bg-emerald-900/40 dark:text-emerald-400'\n : 'bg-muted text-muted-foreground'\n }`}>\n <svg className=\"h-4 w-4\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth={2}>\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" d=\"M4 7v10c0 2.21 3.582 4 8 4s8-1.79 8-4V7M4 7c0 2.21 3.582 4 8 4s8-1.79 8-4M4 7c0-2.21 3.582-4 8-4s8 1.79 8 4m0 5c0 2.21-3.582 4-8 4s-8-1.79-8-4\" />\n </svg>\n </div>\n <div className=\"flex-1 min-w-0\">\n <div className=\"flex items-center gap-2\">\n <p className={`text-sm font-medium ${isCurrent && isReady ? 'text-emerald-700 dark:text-emerald-300' : ''}`}>\n {driver.name}\n </p>\n {isCurrent && (\n <span className=\"text-[10px] px-1.5 py-0.5 rounded bg-emerald-100 text-emerald-700 dark:bg-emerald-900/40 dark:text-emerald-300\">\n {t('search.settings.vector.active', 'Active')}\n </span>\n )}\n {!driver.implemented && (\n <span className=\"text-[10px] px-1.5 py-0.5 rounded bg-muted text-muted-foreground\">\n {t('search.settings.vector.comingSoon', 'Coming soon')}\n </span>\n )}\n </div>\n <div className=\"mt-1 space-y-0.5\">\n {driver.envVars.map((envVar) => (\n <div key={envVar.name} className=\"flex items-center gap-1.5\">\n <div className={`h-1.5 w-1.5 rounded-full ${envVar.set ? 'bg-emerald-500' : 'bg-muted-foreground/40'}`} />\n <code className=\"text-[10px] text-muted-foreground font-mono\">{envVar.name}</code>\n </div>\n ))}\n </div>\n </div>\n </div>\n )\n })}\n </div>\n </div>\n\n {/* Embedding Provider Selection */}\n <div>\n <h3 className=\"text-sm font-semibold mb-2\">{t('search.settings.vector.providers', 'Embedding Provider')}</h3>\n <p className=\"text-xs text-muted-foreground mb-3\">{t('search.settings.vector.providersHint', 'Select a provider to generate embeddings. Only providers with configured API keys can be selected.')}</p>\n <div className=\"grid gap-3 sm:grid-cols-2 lg:grid-cols-3 items-start\">\n {providerOptions.map((providerId) => {\n const info = EMBEDDING_PROVIDERS[providerId]\n const isConfigured = embeddingSettings?.configuredProviders?.includes(providerId)\n const isSelected = displayProvider === providerId\n const isCurrentlySaved = savedProvider === providerId\n return (\n <button\n key={providerId}\n type=\"button\"\n onClick={() => isConfigured && handleProviderChange(providerId)}\n disabled={!isConfigured || embeddingLoading || embeddingSaving}\n className={`text-left p-3 rounded-lg border-2 transition-all ${\n isSelected\n ? 'border-primary bg-primary/5 ring-1 ring-primary/20'\n : isConfigured\n ? 'border-border hover:border-primary/50 hover:bg-muted/50 cursor-pointer'\n : 'border-border bg-muted/20 opacity-50 cursor-not-allowed'\n }`}\n >\n <div className=\"flex items-start justify-between gap-2\">\n <div className=\"flex-1 min-w-0\">\n <div className=\"flex items-center gap-2\">\n <p className={`text-sm font-medium ${isSelected ? 'text-primary' : isConfigured ? '' : 'text-muted-foreground'}`}>\n {info.name}\n </p>\n {isCurrentlySaved && isConfigured && (\n <span className=\"text-[10px] px-1.5 py-0.5 rounded bg-emerald-100 text-emerald-700 dark:bg-emerald-900/40 dark:text-emerald-300\">\n {t('search.settings.vector.active', 'Active')}\n </span>\n )}\n </div>\n {isConfigured ? (\n <p className=\"text-xs text-muted-foreground mt-1\">\n {info.models.length} {t('search.settings.vector.modelsAvailable', 'models available')}\n </p>\n ) : (\n <p className=\"text-xs text-muted-foreground mt-1\">\n {t('search.settings.vector.setEnvVar', 'Set')} <code className=\"font-mono text-[10px] bg-muted px-1 rounded\">{info.envKeyRequired}</code>\n </p>\n )}\n </div>\n <div className={`flex h-5 w-5 items-center justify-center rounded-full flex-shrink-0 ${\n isSelected\n ? 'bg-primary text-primary-foreground'\n : isConfigured\n ? 'bg-emerald-100 text-emerald-600 dark:bg-emerald-900/40 dark:text-emerald-400'\n : 'bg-muted text-muted-foreground'\n }`}>\n {isSelected ? (\n <svg className=\"h-3 w-3\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" strokeWidth={3}>\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" d=\"M5 13l4 4L19 7\" />\n </svg>\n ) : isConfigured ? (\n <svg className=\"h-3 w-3\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" strokeWidth={2}>\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" d=\"M5 13l4 4L19 7\" />\n </svg>\n ) : (\n <svg className=\"h-3 w-3\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" strokeWidth={2}>\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" d=\"M12 4v16m8-8H4\" />\n </svg>\n )}\n </div>\n </div>\n\n {/* Model Selection */}\n {isSelected && isConfigured && (\n <div className=\"mt-3 pt-3 border-t border-border space-y-2\" onClick={(e) => e.stopPropagation()} onKeyDown={(e) => e.stopPropagation()} role=\"presentation\">\n <div className=\"space-y-1\">\n <Label htmlFor={`model-${providerId}`} className=\"text-xs font-medium\">\n {t('search.settings.model.label', 'Model')}\n </Label>\n <select\n id={`model-${providerId}`}\n className=\"w-full rounded-md border border-input bg-background px-2 py-1.5 text-sm focus:outline-none focus:ring-2 focus:ring-ring disabled:opacity-60\"\n value={displayModel}\n onChange={(e) => handleModelChange(e.target.value)}\n disabled={embeddingLoading || embeddingSaving}\n >\n {savedCustomModel && displayProvider === savedProvider && (\n <option key={savedCustomModel.id} value={savedCustomModel.id}>\n {savedCustomModel.name} ({savedCustomModel.dimension}d)\n </option>\n )}\n {displayProviderInfo.models.map((model) => (\n <option key={model.id} value={model.id}>\n {model.name} ({model.dimension}d)\n </option>\n ))}\n <option value=\"custom\">{t('search.settings.model.custom', 'Custom...')}</option>\n </select>\n </div>\n\n {isCustomModel && (\n <div className=\"space-y-2 p-2 rounded border border-input bg-muted/30\">\n <input\n type=\"text\"\n className=\"w-full rounded border border-input bg-background px-2 py-1 text-sm\"\n value={customModelName}\n onChange={(e) => setCustomModelName(e.target.value)}\n placeholder={t('search.settings.model.namePlaceholder', 'Model name')}\n disabled={embeddingLoading || embeddingSaving}\n />\n <input\n type=\"number\"\n className=\"w-full rounded border border-input bg-background px-2 py-1 text-sm\"\n value={customDimension}\n onChange={(e) => setCustomDimension(Number(e.target.value) || 768)}\n placeholder=\"768\"\n min={1}\n disabled={embeddingLoading || embeddingSaving}\n />\n </div>\n )}\n\n <div className=\"flex items-center justify-between text-xs\">\n <span className=\"text-muted-foreground\">\n {t('search.settings.dimension.label', 'Dimensions')}: {displayDimension}\n </span>\n {embeddingSettings?.indexedDimension && embeddingSettings.indexedDimension !== displayDimension && (\n <span className=\"text-amber-600 dark:text-amber-400\">\n {t('search.settings.dimension.mismatch', 'mismatch')}: {embeddingSettings.indexedDimension}\n </span>\n )}\n </div>\n\n {hasUnsavedEmbeddingChanges && (\n <div className=\"flex gap-2 pt-1\">\n <Button type=\"button\" variant=\"default\" size=\"sm\" className=\"flex-1\" onClick={handleApplyEmbeddingChanges} disabled={embeddingLoading || embeddingSaving}>\n {embeddingSaving ? <Spinner size=\"sm\" className=\"mr-1\" /> : null}\n {t('search.settings.actions.apply', 'Apply')}\n </Button>\n <Button type=\"button\" variant=\"outline\" size=\"sm\" onClick={handleCancelEmbeddingSelection} disabled={embeddingLoading || embeddingSaving}>\n {t('search.settings.actions.cancel', 'Cancel')}\n </Button>\n </div>\n )}\n </div>\n )}\n </button>\n )\n })}\n </div>\n </div>\n\n {/* Setup Instructions */}\n <div className=\"p-3 rounded-md bg-blue-50 dark:bg-blue-900/20 border border-blue-200 dark:border-blue-800\">\n <div className=\"flex items-start gap-2\">\n <svg className=\"h-5 w-5 text-blue-600 dark:text-blue-400 flex-shrink-0 mt-0.5\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z\" />\n </svg>\n <div className=\"text-sm text-blue-800 dark:text-blue-200\">\n <p className=\"font-medium mb-1\">{t('search.settings.vector.howTo', 'How to set up')}</p>\n <p className=\"text-xs\">{t('search.settings.vector.howToDescription', 'Add the API key for your preferred provider to your .env file. Only providers with configured API keys can be selected.')}</p>\n </div>\n </div>\n </div>\n </div>\n )}\n </TabsContent>\n\n {/* Index Management Tab */}\n <TabsContent value=\"index\">\n {embeddingLoading ? (\n <div className=\"flex items-center gap-2 text-muted-foreground\">\n <Spinner size=\"sm\" />\n <span>{t('search.settings.loadingLabel', 'Loading settings...')}</span>\n </div>\n ) : !isEmbeddingConfigured ? (\n <div className=\"p-4 rounded-md bg-amber-50 dark:bg-amber-900/20 border border-amber-200 dark:border-amber-800\">\n <div className=\"flex items-start gap-3\">\n <svg className=\"h-5 w-5 text-amber-600 dark:text-amber-400 flex-shrink-0 mt-0.5\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z\" />\n </svg>\n <div>\n <p className=\"text-sm font-medium text-amber-800 dark:text-amber-200\">\n {t('search.settings.vectorNotConfigured', 'No embedding provider configured')}\n </p>\n <p className=\"text-xs text-amber-700 dark:text-amber-300 mt-1\">\n {t('search.settings.vectorNotConfiguredHint', 'Configure an embedding provider in the Configuration tab to enable indexing.')}\n </p>\n </div>\n </div>\n </div>\n ) : (\n <div className=\"space-y-4\">\n {/* Document Count */}\n {embeddingSettings?.documentCount !== null && embeddingSettings?.documentCount !== undefined && (\n <div className=\"rounded-md border border-border p-4 max-w-xs\">\n <p className=\"text-sm text-muted-foreground\">{t('search.settings.vectorDocumentsLabel', 'Embeddings')}</p>\n <p className=\"text-2xl font-bold\">{embeddingSettings.documentCount.toLocaleString()}</p>\n </div>\n )}\n\n {/* Auto-Indexing Toggle */}\n <div className=\"flex items-start gap-4 p-4 rounded-md border border-border\">\n <div className=\"flex-1\">\n <div className=\"flex items-center gap-2\">\n <input\n id=\"search-auto-indexing\"\n type=\"checkbox\"\n className=\"h-4 w-4 rounded border-muted-foreground/40\"\n checked={autoIndexingChecked}\n onChange={(event) => updateAutoIndexing(event.target.checked)}\n disabled={autoIndexingDisabled}\n />\n <Label htmlFor=\"search-auto-indexing\" className=\"text-sm font-medium\">\n {t('search.settings.autoIndexing.label', 'Enable auto-indexing')}\n </Label>\n {embeddingSaving ? <Spinner size=\"sm\" className=\"text-muted-foreground\" /> : null}\n </div>\n <p className=\"text-xs text-muted-foreground mt-1 ml-6\">\n {t('search.settings.autoIndexing.description', 'Automatically index new and updated records for vector search.')}\n </p>\n {embeddingSettings?.autoIndexingLocked && (\n <p className=\"text-xs text-destructive mt-1 ml-6\">\n {t('search.settings.autoIndexing.locked', 'Disabled via environment variable.')}\n </p>\n )}\n </div>\n </div>\n\n {/* Reindex Actions */}\n <div className=\"space-y-3\">\n <h3 className=\"text-sm font-semibold\">{t('search.settings.vectorReindex.title', 'Reindex Data')}</h3>\n <p className=\"text-xs text-muted-foreground\">\n {t('search.settings.vectorReindex.description', 'Rebuild vector embeddings for all indexed entities. This will purge existing data and regenerate all embeddings.')}\n </p>\n\n {/* Active reindex lock banner */}\n {vectorReindexLock && (\n <div className=\"p-3 rounded-md bg-blue-50 dark:bg-blue-900/20 border border-blue-200 dark:border-blue-800\">\n <div className=\"flex items-start gap-3\">\n <Spinner size=\"sm\" className=\"flex-shrink-0 mt-0.5 text-blue-600 dark:text-blue-400\" />\n <div className=\"flex-1\">\n <p className=\"text-sm font-medium text-blue-800 dark:text-blue-200\">\n {t('search.settings.reindexInProgress', 'Reindex operation in progress')}\n </p>\n <p className=\"text-xs text-blue-700 dark:text-blue-300 mt-1\">\n {t('search.settings.reindexInProgressDetails', 'Action: {{action}} | Started {{minutes}} minutes ago', {\n action: vectorReindexLock.action,\n minutes: vectorReindexLock.elapsedMinutes,\n })}\n </p>\n </div>\n </div>\n </div>\n )}\n\n <div className=\"flex items-center gap-2 p-2 rounded bg-amber-50 dark:bg-amber-900/20\">\n <svg className=\"h-4 w-4 text-amber-600 dark:text-amber-400 flex-shrink-0\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z\" />\n </svg>\n <p className=\"text-xs text-amber-800 dark:text-amber-200\">\n {t('search.settings.vectorReindex.warning', 'This may take a while for large datasets and will consume API credits.')}\n </p>\n </div>\n <Button\n type=\"button\"\n variant=\"default\"\n size=\"sm\"\n onClick={handleVectorReindexClick}\n disabled={embeddingLoading || embeddingSaving || vectorReindexing || vectorReindexLock !== null}\n >\n {vectorReindexing || vectorReindexLock !== null ? (\n <>\n <Spinner size=\"sm\" className=\"mr-2\" />\n {t('search.settings.vectorReindex.running', 'Reindexing...')}\n </>\n ) : (\n t('search.settings.vectorReindex.button', 'Full Reindex')\n )}\n </Button>\n </div>\n </div>\n )}\n </TabsContent>\n\n {/* Activity Tab */}\n <TabsContent value=\"activity\">\n {activityLoading ? (\n <div className=\"flex items-center gap-2 text-muted-foreground\">\n <Spinner size=\"sm\" />\n <span>{t('search.settings.loadingLabel', 'Loading...')}</span>\n </div>\n ) : activityLogs.length === 0 ? (\n <div className=\"p-4 rounded-md bg-muted/50 text-center\">\n <p className=\"text-sm text-muted-foreground\">\n {t('search.settings.activity.noLogs', 'No recent indexing activity')}\n </p>\n </div>\n ) : (\n <div className=\"space-y-2 max-h-80 overflow-y-auto\">\n {activityLogs.map((log) => (\n <div\n key={log.id}\n className={`p-2 rounded-md text-sm ${\n log.level === 'error'\n ? 'bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-800'\n : 'bg-muted/50'\n }`}\n >\n <div className=\"flex items-start gap-2\">\n {log.level === 'error' && (\n <svg className=\"h-4 w-4 text-red-600 dark:text-red-400 flex-shrink-0 mt-0.5\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z\" />\n </svg>\n )}\n <div className=\"flex-1 min-w-0\">\n <p className={`text-xs ${log.level === 'error' ? 'text-red-800 dark:text-red-200' : 'text-foreground'}`}>\n {log.message}\n </p>\n <p className=\"text-xs text-muted-foreground mt-0.5\">\n {(() => {\n const d = new Date(log.occurredAt)\n const pad = (n: number) => n.toString().padStart(2, '0')\n return `${pad(d.getDate())}-${pad(d.getMonth() + 1)}-${d.getFullYear()} ${pad(d.getHours())}:${pad(d.getMinutes())}`\n })()}\n {log.entityType && ` \u00B7 ${log.entityType}`}\n </p>\n </div>\n </div>\n </div>\n ))}\n </div>\n )}\n <div className=\"mt-3\">\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n onClick={fetchActivityLogs}\n disabled={activityLoading}\n >\n {activityLoading ? (\n <>\n <Spinner size=\"sm\" className=\"mr-2\" />\n {t('search.settings.loadingLabel', 'Loading...')}\n </>\n ) : (\n t('search.settings.refreshLabel', 'Refresh')\n )}\n </Button>\n </div>\n </TabsContent>\n </Tabs>\n\n {/* Vector Reindex Confirmation Dialog */}\n {showVectorReindexDialog && (\n <div className=\"fixed inset-0 z-50 flex items-center justify-center bg-black/50\">\n <div className=\"mx-4 max-w-md rounded-lg border border-border bg-card p-6 shadow-lg\">\n <h3 className=\"text-lg font-semibold mb-2\">{t('search.settings.reindex.confirmTitle', 'Confirm Reindex')}</h3>\n <p className=\"text-sm text-muted-foreground mb-4\">\n {t('search.settings.reindex.confirmDescription', 'This will rebuild all vector embeddings. Existing data will be purged first.')}\n </p>\n <div className=\"flex justify-end gap-3\">\n <Button type=\"button\" variant=\"outline\" onClick={handleVectorReindexCancel}>\n {t('search.settings.actions.cancel', 'Cancel')}\n </Button>\n <Button type=\"button\" variant=\"default\" onClick={handleVectorReindexConfirm}>\n {t('search.settings.reindex.confirmButton', 'Start Reindex')}\n </Button>\n </div>\n </div>\n </div>\n )}\n\n {/* Embedding Provider Change Confirmation Dialog */}\n {showEmbeddingConfirmDialog && pendingEmbeddingConfig && (\n <div className=\"fixed inset-0 z-50 flex items-center justify-center bg-black/50\">\n <div className=\"mx-4 max-w-lg rounded-lg border border-border bg-card p-6 shadow-lg\">\n <h3 className=\"text-lg font-semibold mb-2\">{t('search.settings.change.title', 'Confirm Provider Change')}</h3>\n <p className=\"text-sm text-muted-foreground mb-4\">\n {t('search.settings.change.description', 'Changing the embedding provider will require reindexing all data.')}\n </p>\n <div className=\"mb-4 p-3 rounded-md bg-muted/50 text-sm\">\n <p className=\"font-medium\">\n {embeddingSettings?.embeddingConfig\n ? `${EMBEDDING_PROVIDERS[embeddingSettings.embeddingConfig.providerId].name} (${embeddingSettings.embeddingConfig.model})`\n : 'Default'}\n {' \u2192 '}\n {EMBEDDING_PROVIDERS[pendingEmbeddingConfig.providerId].name} ({pendingEmbeddingConfig.model})\n </p>\n <p className=\"text-muted-foreground\">\n {embeddingSettings?.indexedDimension ?? 'N/A'} \u2192 {pendingEmbeddingConfig.dimension} dimensions\n </p>\n </div>\n <ul className=\"mb-4 space-y-1 text-sm\">\n <li className=\"flex items-start gap-2\">\n <span className=\"text-destructive\">\u2022</span>\n <span>{t('search.settings.change.bullet1', 'Existing vector data will be cleared')}</span>\n </li>\n <li className=\"flex items-start gap-2\">\n <span className=\"text-destructive\">\u2022</span>\n <span>{t('search.settings.change.bullet2', 'Vector search will be unavailable during reindex')}</span>\n </li>\n </ul>\n <div className=\"flex justify-end gap-3\">\n <Button type=\"button\" variant=\"outline\" onClick={handleEmbeddingCancelChange} disabled={embeddingSaving}>\n {t('search.settings.actions.cancel', 'Cancel')}\n </Button>\n <Button type=\"button\" variant=\"destructive\" onClick={handleEmbeddingConfirmChange} disabled={embeddingSaving}>\n {embeddingSaving ? <Spinner size=\"sm\" className=\"mr-2\" /> : null}\n {t('search.settings.actions.confirm', 'Confirm')}\n </Button>\n </div>\n </div>\n </div>\n )}\n </div>\n )\n}\n\nexport default VectorSearchSection\n"],
5
- "mappings": "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",
4
+ "sourcesContent": ["'use client'\n\nimport * as React from 'react'\nimport { useT } from '@open-mercato/shared/lib/i18n/context'\nimport { readApiResultOrThrow } from '@open-mercato/ui/backend/utils/apiCall'\nimport { flash } from '@open-mercato/ui/backend/FlashMessages'\nimport { Button } from '@open-mercato/ui/primitives/button'\nimport { Label } from '@open-mercato/ui/primitives/label'\nimport { Spinner } from '@open-mercato/ui/primitives/spinner'\nimport { Tabs, TabsList, TabsTrigger, TabsContent } from '@open-mercato/ui/primitives/tabs'\n\n// Types\ntype EmbeddingProviderId = 'openai' | 'google' | 'mistral' | 'cohere' | 'bedrock' | 'ollama'\n\ntype EmbeddingProviderConfig = {\n providerId: EmbeddingProviderId\n model: string\n dimension: number\n outputDimensionality?: number\n baseUrl?: string\n updatedAt: string\n}\n\ntype EmbeddingModelInfo = {\n id: string\n name: string\n dimension: number\n configurableDimension?: boolean\n minDimension?: number\n maxDimension?: number\n}\n\ntype EmbeddingProviderInfo = {\n name: string\n envKeyRequired: string\n defaultModel: string\n models: EmbeddingModelInfo[]\n}\n\ntype EmbeddingSettings = {\n openaiConfigured: boolean\n autoIndexingEnabled: boolean\n autoIndexingLocked: boolean\n lockReason: string | null\n embeddingConfig: EmbeddingProviderConfig | null\n configuredProviders: EmbeddingProviderId[]\n indexedDimension: number | null\n reindexRequired: boolean\n documentCount: number | null\n}\n\ntype EmbeddingSettingsResponse = {\n settings?: EmbeddingSettings\n error?: string\n}\n\ntype VectorDriverId = 'pgvector' | 'qdrant' | 'chromadb'\n\ntype VectorDriverEnvVar = {\n name: string\n set: boolean\n hint: string\n}\n\ntype VectorDriverStatus = {\n id: VectorDriverId\n name: string\n configured: boolean\n implemented: boolean\n envVars: VectorDriverEnvVar[]\n}\n\ntype VectorStoreConfigResponse = {\n currentDriver: VectorDriverId\n configured: boolean\n drivers: VectorDriverStatus[]\n}\n\ntype ReindexLock = {\n type: 'fulltext' | 'vector'\n action: string\n startedAt: string\n elapsedMinutes: number\n}\n\ntype ActivityLog = {\n id: string\n source: string\n handler: string\n level: 'info' | 'error' | 'warn'\n entityType: string | null\n recordId: string | null\n message: string\n details: unknown\n occurredAt: string\n}\n\nconst EMBEDDING_PROVIDERS: Record<EmbeddingProviderId, EmbeddingProviderInfo> = {\n openai: {\n name: 'OpenAI',\n envKeyRequired: 'OPENAI_API_KEY',\n defaultModel: 'text-embedding-3-small',\n models: [\n { id: 'text-embedding-3-small', name: 'text-embedding-3-small', dimension: 1536 },\n { id: 'text-embedding-3-large', name: 'text-embedding-3-large', dimension: 3072, configurableDimension: true, minDimension: 256, maxDimension: 3072 },\n { id: 'text-embedding-ada-002', name: 'text-embedding-ada-002', dimension: 1536 },\n ],\n },\n google: {\n name: 'Google Generative AI',\n envKeyRequired: 'GOOGLE_GENERATIVE_AI_API_KEY',\n defaultModel: 'text-embedding-004',\n models: [\n { id: 'text-embedding-004', name: 'text-embedding-004', dimension: 768, configurableDimension: true, minDimension: 1, maxDimension: 768 },\n { id: 'embedding-001', name: 'embedding-001', dimension: 768 },\n ],\n },\n mistral: {\n name: 'Mistral',\n envKeyRequired: 'MISTRAL_API_KEY',\n defaultModel: 'mistral-embed',\n models: [\n { id: 'mistral-embed', name: 'mistral-embed', dimension: 1024 },\n ],\n },\n cohere: {\n name: 'Cohere',\n envKeyRequired: 'COHERE_API_KEY',\n defaultModel: 'embed-english-v3.0',\n models: [\n { id: 'embed-english-v3.0', name: 'embed-english-v3.0', dimension: 1024 },\n { id: 'embed-multilingual-v3.0', name: 'embed-multilingual-v3.0', dimension: 1024 },\n { id: 'embed-english-light-v3.0', name: 'embed-english-light-v3.0', dimension: 384 },\n { id: 'embed-multilingual-light-v3.0', name: 'embed-multilingual-light-v3.0', dimension: 384 },\n ],\n },\n bedrock: {\n name: 'Amazon Bedrock',\n envKeyRequired: 'AWS_ACCESS_KEY_ID',\n defaultModel: 'amazon.titan-embed-text-v2:0',\n models: [\n { id: 'amazon.titan-embed-text-v2:0', name: 'Titan Embed Text v2', dimension: 1024, configurableDimension: true, minDimension: 256, maxDimension: 1024 },\n { id: 'amazon.titan-embed-text-v1', name: 'Titan Embed Text v1', dimension: 1536 },\n { id: 'cohere.embed-english-v3', name: 'Cohere Embed English v3', dimension: 1024 },\n { id: 'cohere.embed-multilingual-v3', name: 'Cohere Embed Multilingual v3', dimension: 1024 },\n ],\n },\n ollama: {\n name: 'Ollama (Local)',\n envKeyRequired: 'OLLAMA_BASE_URL',\n defaultModel: 'nomic-embed-text',\n models: [\n { id: 'nomic-embed-text', name: 'nomic-embed-text', dimension: 768 },\n { id: 'mxbai-embed-large', name: 'mxbai-embed-large', dimension: 1024 },\n { id: 'all-minilm', name: 'all-minilm', dimension: 384 },\n { id: 'snowflake-arctic-embed', name: 'snowflake-arctic-embed', dimension: 1024 },\n ],\n },\n}\n\nexport type VectorSearchSectionProps = {\n embeddingSettings: EmbeddingSettings | null\n embeddingLoading: boolean\n vectorStoreConfig: VectorStoreConfigResponse | null\n vectorStoreConfigLoading: boolean\n vectorReindexLock: ReindexLock | null\n onEmbeddingSettingsUpdate: (settings: EmbeddingSettings) => void\n onRefreshEmbeddings: () => Promise<void>\n}\n\nexport function VectorSearchSection({\n embeddingSettings,\n embeddingLoading,\n vectorStoreConfig,\n vectorStoreConfigLoading,\n vectorReindexLock,\n onEmbeddingSettingsUpdate,\n onRefreshEmbeddings,\n}: VectorSearchSectionProps) {\n const t = useT()\n const [embeddingSaving, setEmbeddingSaving] = React.useState(false)\n const autoIndexingPreviousRef = React.useRef<boolean>(true)\n\n // Staged embedding selection\n const [selectedProvider, setSelectedProvider] = React.useState<EmbeddingProviderId | null>(null)\n const [selectedModel, setSelectedModel] = React.useState<string | null>(null)\n const [customModelName, setCustomModelName] = React.useState<string>('')\n const [customDimension, setCustomDimension] = React.useState<number>(768)\n\n const [pendingEmbeddingConfig, setPendingEmbeddingConfig] = React.useState<EmbeddingProviderConfig | null>(null)\n const [showEmbeddingConfirmDialog, setShowEmbeddingConfirmDialog] = React.useState(false)\n\n // Vector reindex state\n const [vectorReindexing, setVectorReindexing] = React.useState(false)\n const [showVectorReindexDialog, setShowVectorReindexDialog] = React.useState(false)\n\n // Activity logs state\n const [activityLogs, setActivityLogs] = React.useState<ActivityLog[]>([])\n const [activityLoading, setActivityLoading] = React.useState(true)\n\n // Fetch activity logs\n const fetchActivityLogs = React.useCallback(async () => {\n setActivityLoading(true)\n try {\n const response = await fetch('/api/query_index/status')\n if (response.ok) {\n const body = await response.json() as { logs?: ActivityLog[]; errors?: ActivityLog[] }\n const allLogs: ActivityLog[] = []\n if (body.logs) {\n allLogs.push(...body.logs)\n }\n if (body.errors) {\n allLogs.push(...body.errors.map(err => ({ ...err, level: 'error' as const })))\n }\n // Filter for vector-related logs\n const vectorLogs = allLogs.filter(log => {\n const lowerSource = log.source?.toLowerCase() ?? ''\n const lowerMessage = log.message?.toLowerCase() ?? ''\n const lowerHandler = log.handler?.toLowerCase() ?? ''\n return lowerSource.includes('vector') || lowerMessage.includes('vector') ||\n lowerMessage.includes('embedding') || lowerHandler.includes('vector')\n })\n vectorLogs.sort((a, b) => new Date(b.occurredAt).getTime() - new Date(a.occurredAt).getTime())\n setActivityLogs(vectorLogs.slice(0, 50))\n }\n } catch {\n // Silently fail\n } finally {\n setActivityLoading(false)\n }\n }, [])\n\n React.useEffect(() => {\n fetchActivityLogs()\n }, [fetchActivityLogs])\n\n // Poll for activity when reindexing\n React.useEffect(() => {\n if (vectorReindexLock || vectorReindexing) {\n const interval = setInterval(fetchActivityLogs, 5000)\n return () => clearInterval(interval)\n }\n }, [vectorReindexLock, vectorReindexing, fetchActivityLogs])\n\n // Update auto-indexing\n const updateAutoIndexing = React.useCallback(async (nextValue: boolean) => {\n autoIndexingPreviousRef.current = embeddingSettings?.autoIndexingEnabled ?? true\n if (embeddingSettings) {\n onEmbeddingSettingsUpdate({ ...embeddingSettings, autoIndexingEnabled: nextValue })\n }\n setEmbeddingSaving(true)\n try {\n const body = await readApiResultOrThrow<EmbeddingSettingsResponse>(\n '/api/search/embeddings',\n {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ autoIndexingEnabled: nextValue }),\n },\n { errorMessage: t('search.settings.errors.saveFailed', 'Failed to save settings'), allowNullResult: true },\n )\n if (body?.settings) {\n onEmbeddingSettingsUpdate(body.settings)\n autoIndexingPreviousRef.current = body.settings.autoIndexingEnabled\n }\n flash(t('search.settings.messages.saved', 'Settings saved'), 'success')\n } catch {\n if (embeddingSettings) {\n onEmbeddingSettingsUpdate({ ...embeddingSettings, autoIndexingEnabled: autoIndexingPreviousRef.current })\n }\n } finally {\n setEmbeddingSaving(false)\n }\n }, [embeddingSettings, onEmbeddingSettingsUpdate, t])\n\n // Provider handlers\n const handleProviderChange = (providerId: EmbeddingProviderId) => {\n setSelectedProvider(providerId)\n setSelectedModel(null)\n setCustomModelName('')\n setCustomDimension(768)\n }\n\n const handleModelChange = (modelId: string) => {\n setSelectedModel(modelId)\n }\n\n const handleApplyEmbeddingChanges = () => {\n const newProviderId = selectedProvider ?? embeddingSettings?.embeddingConfig?.providerId ?? 'openai'\n const newProviderInfo = EMBEDDING_PROVIDERS[newProviderId]\n const newModelId = selectedModel ?? (selectedProvider ? newProviderInfo.defaultModel : embeddingSettings?.embeddingConfig?.model ?? newProviderInfo.defaultModel)\n\n let modelName: string\n let dimension: number\n\n if (newModelId === 'custom') {\n modelName = customModelName.trim()\n dimension = customDimension\n if (!modelName) {\n flash(t('search.settings.errors.modelRequired', 'Please enter a model name'), 'error')\n return\n }\n if (dimension <= 0) {\n flash(t('search.settings.errors.dimensionRequired', 'Please enter a valid dimension'), 'error')\n return\n }\n } else {\n const newModel = newProviderInfo.models.find((m) => m.id === newModelId) ?? newProviderInfo.models[0]\n modelName = newModel.id\n dimension = newModel.dimension\n }\n\n const newConfig: EmbeddingProviderConfig = {\n providerId: newProviderId,\n model: modelName,\n dimension,\n updatedAt: new Date().toISOString(),\n }\n\n if (embeddingSettings?.indexedDimension || embeddingSettings?.embeddingConfig) {\n setPendingEmbeddingConfig(newConfig)\n setShowEmbeddingConfirmDialog(true)\n } else {\n applyEmbeddingConfig(newConfig)\n }\n }\n\n const handleCancelEmbeddingSelection = () => {\n setSelectedProvider(null)\n setSelectedModel(null)\n setCustomModelName('')\n setCustomDimension(768)\n }\n\n const applyEmbeddingConfig = async (config: EmbeddingProviderConfig) => {\n setEmbeddingSaving(true)\n setShowEmbeddingConfirmDialog(false)\n setPendingEmbeddingConfig(null)\n\n try {\n await readApiResultOrThrow<EmbeddingSettingsResponse>(\n '/api/search/embeddings',\n {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ embeddingConfig: config }),\n },\n { errorMessage: t('search.settings.errors.saveFailed', 'Failed to save settings'), allowNullResult: true },\n )\n setSelectedProvider(null)\n setSelectedModel(null)\n flash(t('search.settings.messages.providerSaved', 'Embedding provider saved'), 'success')\n await onRefreshEmbeddings()\n } catch {\n // Error handled by readApiResultOrThrow\n } finally {\n setEmbeddingSaving(false)\n }\n }\n\n const handleEmbeddingConfirmChange = () => {\n if (pendingEmbeddingConfig) {\n applyEmbeddingConfig(pendingEmbeddingConfig)\n }\n }\n\n const handleEmbeddingCancelChange = () => {\n setShowEmbeddingConfirmDialog(false)\n setPendingEmbeddingConfig(null)\n }\n\n // Vector reindex handlers\n const handleVectorReindexClick = () => {\n setShowVectorReindexDialog(true)\n }\n\n const handleVectorReindexConfirm = async () => {\n setShowVectorReindexDialog(false)\n setVectorReindexing(true)\n try {\n await readApiResultOrThrow<{ ok: boolean }>(\n '/api/search/embeddings/reindex',\n {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({ purgeFirst: true }),\n },\n { errorMessage: t('search.settings.errors.reindexFailed', 'Reindex failed'), allowNullResult: true },\n )\n flash(t('search.settings.messages.reindexStarted', 'Reindex started'), 'success')\n await fetchActivityLogs()\n } catch {\n // Error handled by readApiResultOrThrow\n } finally {\n setVectorReindexing(false)\n }\n }\n\n const handleVectorReindexCancel = () => {\n setShowVectorReindexDialog(false)\n }\n\n // Computed values\n const savedProvider = embeddingSettings?.embeddingConfig?.providerId ?? 'openai'\n const savedProviderInfo = EMBEDDING_PROVIDERS[savedProvider]\n const savedModel = embeddingSettings?.embeddingConfig?.model ?? savedProviderInfo.defaultModel\n const savedDimension = embeddingSettings?.embeddingConfig?.dimension ?? savedProviderInfo.models[0]?.dimension ?? 768\n\n const savedModelIsPredefined = savedProviderInfo.models.some((m) => m.id === savedModel)\n const savedCustomModel = !savedModelIsPredefined && savedModel ? { id: savedModel, name: savedModel, dimension: savedDimension } : null\n\n const displayProvider = selectedProvider ?? savedProvider\n const displayProviderInfo = EMBEDDING_PROVIDERS[displayProvider]\n const displayModel = selectedModel ?? (selectedProvider ? displayProviderInfo.defaultModel : savedModel)\n const isCustomModel = displayModel === 'custom'\n\n const displayModelIsSavedCustom = !isCustomModel && displayProvider === savedProvider && savedCustomModel && displayModel === savedCustomModel.id\n\n const displayModelInfo = isCustomModel\n ? null\n : displayModelIsSavedCustom\n ? savedCustomModel\n : displayProviderInfo.models.find((m) => m.id === displayModel) ?? displayProviderInfo.models[0]\n const displayDimension = isCustomModel ? customDimension : (displayModelInfo?.dimension ?? 768)\n\n const hasUnsavedEmbeddingChanges = (selectedProvider !== null && selectedProvider !== savedProvider) ||\n (selectedModel !== null && selectedModel !== savedModel) ||\n (selectedProvider !== null && selectedModel === null && displayProviderInfo.defaultModel !== savedModel) ||\n (isCustomModel && (customModelName.trim() !== '' || customDimension !== 768))\n\n const isEmbeddingConfigured = embeddingSettings?.configuredProviders?.includes(savedProvider)\n const providerOptions: EmbeddingProviderId[] = ['openai', 'google', 'mistral', 'cohere', 'bedrock', 'ollama']\n\n const autoIndexingChecked = embeddingSettings ? embeddingSettings.autoIndexingEnabled : true\n const autoIndexingDisabled = embeddingLoading || embeddingSaving || Boolean(embeddingSettings?.autoIndexingLocked)\n\n return (\n <div className=\"rounded-lg border border-border bg-card p-5 shadow-sm\">\n <h2 className=\"text-lg font-semibold mb-2\">\n {t('search.settings.vector.sectionTitle', 'Vector Search')}\n </h2>\n <p className=\"text-sm text-muted-foreground mb-4\">\n {t('search.settings.vector.sectionDescription', 'AI-powered semantic search using embeddings.')}\n </p>\n\n <Tabs defaultValue=\"configuration\">\n <TabsList className=\"mb-4\">\n <TabsTrigger value=\"configuration\">\n {t('search.settings.tabs.configuration', 'Configuration')}\n </TabsTrigger>\n <TabsTrigger value=\"index\">\n {t('search.settings.tabs.indexManagement', 'Index Management')}\n </TabsTrigger>\n <TabsTrigger value=\"activity\">\n {t('search.settings.tabs.activity', 'Activity')}\n </TabsTrigger>\n </TabsList>\n\n {/* Configuration Tab */}\n <TabsContent value=\"configuration\">\n {(embeddingLoading || vectorStoreConfigLoading) ? (\n <div className=\"flex items-center gap-2 text-muted-foreground\">\n <Spinner size=\"sm\" />\n <span>{t('search.settings.loadingLabel', 'Loading settings...')}</span>\n </div>\n ) : (\n <div className=\"space-y-4\">\n {/* Vector Store Driver Status */}\n <div>\n <h3 className=\"text-sm font-semibold mb-2\">{t('search.settings.vector.store', 'Vector Store')}</h3>\n <div className=\"grid gap-2 sm:grid-cols-3\">\n {vectorStoreConfig?.drivers.map((driver) => {\n const isCurrent = driver.id === vectorStoreConfig.currentDriver\n const isReady = driver.configured && driver.implemented\n return (\n <div\n key={driver.id}\n className={`flex items-start gap-3 p-3 rounded-md border ${\n isCurrent && isReady\n ? 'border-emerald-200 bg-emerald-50 dark:border-emerald-800 dark:bg-emerald-900/20'\n : !driver.implemented\n ? 'border-border bg-muted/20 opacity-60'\n : 'border-border bg-muted/30'\n }`}\n >\n <div className={`flex h-8 w-8 items-center justify-center rounded-full flex-shrink-0 ${\n isCurrent && isReady\n ? 'bg-emerald-100 text-emerald-600 dark:bg-emerald-900/40 dark:text-emerald-400'\n : 'bg-muted text-muted-foreground'\n }`}>\n <svg className=\"h-4 w-4\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" strokeWidth={2}>\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" d=\"M4 7v10c0 2.21 3.582 4 8 4s8-1.79 8-4V7M4 7c0 2.21 3.582 4 8 4s8-1.79 8-4M4 7c0-2.21 3.582-4 8-4s8 1.79 8 4m0 5c0 2.21-3.582 4-8 4s-8-1.79-8-4\" />\n </svg>\n </div>\n <div className=\"flex-1 min-w-0\">\n <div className=\"flex items-center gap-2\">\n <p className={`text-sm font-medium ${isCurrent && isReady ? 'text-emerald-700 dark:text-emerald-300' : ''}`}>\n {driver.name}\n </p>\n {isCurrent && (\n <span className=\"text-[10px] px-1.5 py-0.5 rounded bg-emerald-100 text-emerald-700 dark:bg-emerald-900/40 dark:text-emerald-300\">\n {t('search.settings.vector.active', 'Active')}\n </span>\n )}\n {!driver.implemented && (\n <span className=\"text-[10px] px-1.5 py-0.5 rounded bg-muted text-muted-foreground\">\n {t('search.settings.vector.comingSoon', 'Coming soon')}\n </span>\n )}\n </div>\n <div className=\"mt-1 space-y-0.5\">\n {driver.envVars.map((envVar) => (\n <div key={envVar.name} className=\"flex items-center gap-1.5\">\n <div className={`h-1.5 w-1.5 rounded-full ${envVar.set ? 'bg-emerald-500' : 'bg-muted-foreground/40'}`} />\n <code className=\"text-[10px] text-muted-foreground font-mono\">{envVar.name}</code>\n </div>\n ))}\n </div>\n </div>\n </div>\n )\n })}\n </div>\n </div>\n\n {/* Embedding Provider Selection */}\n <div>\n <h3 className=\"text-sm font-semibold mb-2\">{t('search.settings.vector.providers', 'Embedding Provider')}</h3>\n <p className=\"text-xs text-muted-foreground mb-3\">{t('search.settings.vector.providersHint', 'Select a provider to generate embeddings. Only providers with configured API keys can be selected.')}</p>\n <div className=\"grid gap-3 sm:grid-cols-2 lg:grid-cols-3 items-start\">\n {providerOptions.map((providerId) => {\n const info = EMBEDDING_PROVIDERS[providerId]\n const isConfigured = embeddingSettings?.configuredProviders?.includes(providerId)\n const isSelected = displayProvider === providerId\n const isCurrentlySaved = savedProvider === providerId\n return (\n <button\n key={providerId}\n type=\"button\"\n onClick={() => isConfigured && handleProviderChange(providerId)}\n disabled={!isConfigured || embeddingLoading || embeddingSaving}\n className={`text-left p-3 rounded-lg border-2 transition-all ${\n isSelected\n ? 'border-primary bg-primary/5 ring-1 ring-primary/20'\n : isConfigured\n ? 'border-border hover:border-primary/50 hover:bg-muted/50 cursor-pointer'\n : 'border-border bg-muted/20 opacity-50 cursor-not-allowed'\n }`}\n >\n <div className=\"flex items-start justify-between gap-2\">\n <div className=\"flex-1 min-w-0\">\n <div className=\"flex items-center gap-2\">\n <p className={`text-sm font-medium ${isSelected ? 'text-primary' : isConfigured ? '' : 'text-muted-foreground'}`}>\n {info.name}\n </p>\n {isCurrentlySaved && isConfigured && (\n <span className=\"text-[10px] px-1.5 py-0.5 rounded bg-emerald-100 text-emerald-700 dark:bg-emerald-900/40 dark:text-emerald-300\">\n {t('search.settings.vector.active', 'Active')}\n </span>\n )}\n </div>\n {isConfigured ? (\n <p className=\"text-xs text-muted-foreground mt-1\">\n {info.models.length} {t('search.settings.vector.modelsAvailable', 'models available')}\n </p>\n ) : (\n <p className=\"text-xs text-muted-foreground mt-1\">\n {t('search.settings.vector.setEnvVar', 'Set')} <code className=\"font-mono text-[10px] bg-muted px-1 rounded\">{info.envKeyRequired}</code>\n </p>\n )}\n </div>\n <div className={`flex h-5 w-5 items-center justify-center rounded-full flex-shrink-0 ${\n isSelected\n ? 'bg-primary text-primary-foreground'\n : isConfigured\n ? 'bg-emerald-100 text-emerald-600 dark:bg-emerald-900/40 dark:text-emerald-400'\n : 'bg-muted text-muted-foreground'\n }`}>\n {isSelected ? (\n <svg className=\"h-3 w-3\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" strokeWidth={3}>\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" d=\"M5 13l4 4L19 7\" />\n </svg>\n ) : isConfigured ? (\n <svg className=\"h-3 w-3\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" strokeWidth={2}>\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" d=\"M5 13l4 4L19 7\" />\n </svg>\n ) : (\n <svg className=\"h-3 w-3\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" strokeWidth={2}>\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" d=\"M12 4v16m8-8H4\" />\n </svg>\n )}\n </div>\n </div>\n\n {/* Model Selection */}\n {isSelected && isConfigured && (\n <div className=\"mt-3 pt-3 border-t border-border space-y-2\" onClick={(e) => e.stopPropagation()}>\n <div className=\"space-y-1\">\n <Label htmlFor={`model-${providerId}`} className=\"text-xs font-medium\">\n {t('search.settings.model.label', 'Model')}\n </Label>\n <select\n id={`model-${providerId}`}\n className=\"w-full rounded-md border border-input bg-background px-2 py-1.5 text-sm focus:outline-none focus:ring-2 focus:ring-ring disabled:opacity-60\"\n value={displayModel}\n onChange={(e) => handleModelChange(e.target.value)}\n disabled={embeddingLoading || embeddingSaving}\n >\n {savedCustomModel && displayProvider === savedProvider && (\n <option key={savedCustomModel.id} value={savedCustomModel.id}>\n {savedCustomModel.name} ({savedCustomModel.dimension}d)\n </option>\n )}\n {displayProviderInfo.models.map((model) => (\n <option key={model.id} value={model.id}>\n {model.name} ({model.dimension}d)\n </option>\n ))}\n <option value=\"custom\">{t('search.settings.model.custom', 'Custom...')}</option>\n </select>\n </div>\n\n {isCustomModel && (\n <div className=\"space-y-2 p-2 rounded border border-input bg-muted/30\">\n <input\n type=\"text\"\n className=\"w-full rounded border border-input bg-background px-2 py-1 text-sm\"\n value={customModelName}\n onChange={(e) => setCustomModelName(e.target.value)}\n placeholder={t('search.settings.model.namePlaceholder', 'Model name')}\n disabled={embeddingLoading || embeddingSaving}\n />\n <input\n type=\"number\"\n className=\"w-full rounded border border-input bg-background px-2 py-1 text-sm\"\n value={customDimension}\n onChange={(e) => setCustomDimension(Number(e.target.value) || 768)}\n placeholder=\"768\"\n min={1}\n disabled={embeddingLoading || embeddingSaving}\n />\n </div>\n )}\n\n <div className=\"flex items-center justify-between text-xs\">\n <span className=\"text-muted-foreground\">\n {t('search.settings.dimension.label', 'Dimensions')}: {displayDimension}\n </span>\n {embeddingSettings?.indexedDimension && embeddingSettings.indexedDimension !== displayDimension && (\n <span className=\"text-amber-600 dark:text-amber-400\">\n {t('search.settings.dimension.mismatch', 'mismatch')}: {embeddingSettings.indexedDimension}\n </span>\n )}\n </div>\n\n {hasUnsavedEmbeddingChanges && (\n <div className=\"flex gap-2 pt-1\">\n <Button type=\"button\" variant=\"default\" size=\"sm\" className=\"flex-1\" onClick={handleApplyEmbeddingChanges} disabled={embeddingLoading || embeddingSaving}>\n {embeddingSaving ? <Spinner size=\"sm\" className=\"mr-1\" /> : null}\n {t('search.settings.actions.apply', 'Apply')}\n </Button>\n <Button type=\"button\" variant=\"outline\" size=\"sm\" onClick={handleCancelEmbeddingSelection} disabled={embeddingLoading || embeddingSaving}>\n {t('search.settings.actions.cancel', 'Cancel')}\n </Button>\n </div>\n )}\n </div>\n )}\n </button>\n )\n })}\n </div>\n </div>\n\n {/* Setup Instructions */}\n <div className=\"p-3 rounded-md bg-blue-50 dark:bg-blue-900/20 border border-blue-200 dark:border-blue-800\">\n <div className=\"flex items-start gap-2\">\n <svg className=\"h-5 w-5 text-blue-600 dark:text-blue-400 flex-shrink-0 mt-0.5\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z\" />\n </svg>\n <div className=\"text-sm text-blue-800 dark:text-blue-200\">\n <p className=\"font-medium mb-1\">{t('search.settings.vector.howTo', 'How to set up')}</p>\n <p className=\"text-xs\">{t('search.settings.vector.howToDescription', 'Add the API key for your preferred provider to your .env file. Only providers with configured API keys can be selected.')}</p>\n </div>\n </div>\n </div>\n </div>\n )}\n </TabsContent>\n\n {/* Index Management Tab */}\n <TabsContent value=\"index\">\n {embeddingLoading ? (\n <div className=\"flex items-center gap-2 text-muted-foreground\">\n <Spinner size=\"sm\" />\n <span>{t('search.settings.loadingLabel', 'Loading settings...')}</span>\n </div>\n ) : !isEmbeddingConfigured ? (\n <div className=\"p-4 rounded-md bg-amber-50 dark:bg-amber-900/20 border border-amber-200 dark:border-amber-800\">\n <div className=\"flex items-start gap-3\">\n <svg className=\"h-5 w-5 text-amber-600 dark:text-amber-400 flex-shrink-0 mt-0.5\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z\" />\n </svg>\n <div>\n <p className=\"text-sm font-medium text-amber-800 dark:text-amber-200\">\n {t('search.settings.vectorNotConfigured', 'No embedding provider configured')}\n </p>\n <p className=\"text-xs text-amber-700 dark:text-amber-300 mt-1\">\n {t('search.settings.vectorNotConfiguredHint', 'Configure an embedding provider in the Configuration tab to enable indexing.')}\n </p>\n </div>\n </div>\n </div>\n ) : (\n <div className=\"space-y-4\">\n {/* Document Count */}\n {embeddingSettings?.documentCount !== null && embeddingSettings?.documentCount !== undefined && (\n <div className=\"rounded-md border border-border p-4 max-w-xs\">\n <p className=\"text-sm text-muted-foreground\">{t('search.settings.vectorDocumentsLabel', 'Embeddings')}</p>\n <p className=\"text-2xl font-bold\">{embeddingSettings.documentCount.toLocaleString()}</p>\n </div>\n )}\n\n {/* Auto-Indexing Toggle */}\n <div className=\"flex items-start gap-4 p-4 rounded-md border border-border\">\n <div className=\"flex-1\">\n <div className=\"flex items-center gap-2\">\n <input\n id=\"search-auto-indexing\"\n type=\"checkbox\"\n className=\"h-4 w-4 rounded border-muted-foreground/40\"\n checked={autoIndexingChecked}\n onChange={(event) => updateAutoIndexing(event.target.checked)}\n disabled={autoIndexingDisabled}\n />\n <Label htmlFor=\"search-auto-indexing\" className=\"text-sm font-medium\">\n {t('search.settings.autoIndexing.label', 'Enable auto-indexing')}\n </Label>\n {embeddingSaving ? <Spinner size=\"sm\" className=\"text-muted-foreground\" /> : null}\n </div>\n <p className=\"text-xs text-muted-foreground mt-1 ml-6\">\n {t('search.settings.autoIndexing.description', 'Automatically index new and updated records for vector search.')}\n </p>\n {embeddingSettings?.autoIndexingLocked && (\n <p className=\"text-xs text-destructive mt-1 ml-6\">\n {t('search.settings.autoIndexing.locked', 'Disabled via environment variable.')}\n </p>\n )}\n </div>\n </div>\n\n {/* Reindex Actions */}\n <div className=\"space-y-3\">\n <h3 className=\"text-sm font-semibold\">{t('search.settings.vectorReindex.title', 'Reindex Data')}</h3>\n <p className=\"text-xs text-muted-foreground\">\n {t('search.settings.vectorReindex.description', 'Rebuild vector embeddings for all indexed entities. This will purge existing data and regenerate all embeddings.')}\n </p>\n\n {/* Active reindex lock banner */}\n {vectorReindexLock && (\n <div className=\"p-3 rounded-md bg-blue-50 dark:bg-blue-900/20 border border-blue-200 dark:border-blue-800\">\n <div className=\"flex items-start gap-3\">\n <Spinner size=\"sm\" className=\"flex-shrink-0 mt-0.5 text-blue-600 dark:text-blue-400\" />\n <div className=\"flex-1\">\n <p className=\"text-sm font-medium text-blue-800 dark:text-blue-200\">\n {t('search.settings.reindexInProgress', 'Reindex operation in progress')}\n </p>\n <p className=\"text-xs text-blue-700 dark:text-blue-300 mt-1\">\n {t('search.settings.reindexInProgressDetails', 'Action: {{action}} | Started {{minutes}} minutes ago', {\n action: vectorReindexLock.action,\n minutes: vectorReindexLock.elapsedMinutes,\n })}\n </p>\n </div>\n </div>\n </div>\n )}\n\n <div className=\"flex items-center gap-2 p-2 rounded bg-amber-50 dark:bg-amber-900/20\">\n <svg className=\"h-4 w-4 text-amber-600 dark:text-amber-400 flex-shrink-0\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z\" />\n </svg>\n <p className=\"text-xs text-amber-800 dark:text-amber-200\">\n {t('search.settings.vectorReindex.warning', 'This may take a while for large datasets and will consume API credits.')}\n </p>\n </div>\n <Button\n type=\"button\"\n variant=\"default\"\n size=\"sm\"\n onClick={handleVectorReindexClick}\n disabled={embeddingLoading || embeddingSaving || vectorReindexing || vectorReindexLock !== null}\n >\n {vectorReindexing || vectorReindexLock !== null ? (\n <>\n <Spinner size=\"sm\" className=\"mr-2\" />\n {t('search.settings.vectorReindex.running', 'Reindexing...')}\n </>\n ) : (\n t('search.settings.vectorReindex.button', 'Full Reindex')\n )}\n </Button>\n </div>\n </div>\n )}\n </TabsContent>\n\n {/* Activity Tab */}\n <TabsContent value=\"activity\">\n {activityLoading ? (\n <div className=\"flex items-center gap-2 text-muted-foreground\">\n <Spinner size=\"sm\" />\n <span>{t('search.settings.loadingLabel', 'Loading...')}</span>\n </div>\n ) : activityLogs.length === 0 ? (\n <div className=\"p-4 rounded-md bg-muted/50 text-center\">\n <p className=\"text-sm text-muted-foreground\">\n {t('search.settings.activity.noLogs', 'No recent indexing activity')}\n </p>\n </div>\n ) : (\n <div className=\"space-y-2 max-h-80 overflow-y-auto\">\n {activityLogs.map((log) => (\n <div\n key={log.id}\n className={`p-2 rounded-md text-sm ${\n log.level === 'error'\n ? 'bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-800'\n : 'bg-muted/50'\n }`}\n >\n <div className=\"flex items-start gap-2\">\n {log.level === 'error' && (\n <svg className=\"h-4 w-4 text-red-600 dark:text-red-400 flex-shrink-0 mt-0.5\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\">\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" strokeWidth={2} d=\"M12 8v4m0 4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z\" />\n </svg>\n )}\n <div className=\"flex-1 min-w-0\">\n <p className={`text-xs ${log.level === 'error' ? 'text-red-800 dark:text-red-200' : 'text-foreground'}`}>\n {log.message}\n </p>\n <p className=\"text-xs text-muted-foreground mt-0.5\">\n {(() => {\n const d = new Date(log.occurredAt)\n const pad = (n: number) => n.toString().padStart(2, '0')\n return `${pad(d.getDate())}-${pad(d.getMonth() + 1)}-${d.getFullYear()} ${pad(d.getHours())}:${pad(d.getMinutes())}`\n })()}\n {log.entityType && ` \u00B7 ${log.entityType}`}\n </p>\n </div>\n </div>\n </div>\n ))}\n </div>\n )}\n <div className=\"mt-3\">\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n onClick={fetchActivityLogs}\n disabled={activityLoading}\n >\n {activityLoading ? (\n <>\n <Spinner size=\"sm\" className=\"mr-2\" />\n {t('search.settings.loadingLabel', 'Loading...')}\n </>\n ) : (\n t('search.settings.refreshLabel', 'Refresh')\n )}\n </Button>\n </div>\n </TabsContent>\n </Tabs>\n\n {/* Vector Reindex Confirmation Dialog */}\n {showVectorReindexDialog && (\n <div className=\"fixed inset-0 z-50 flex items-center justify-center bg-black/50\">\n <div className=\"mx-4 max-w-md rounded-lg border border-border bg-card p-6 shadow-lg\">\n <h3 className=\"text-lg font-semibold mb-2\">{t('search.settings.reindex.confirmTitle', 'Confirm Reindex')}</h3>\n <p className=\"text-sm text-muted-foreground mb-4\">\n {t('search.settings.reindex.confirmDescription', 'This will rebuild all vector embeddings. Existing data will be purged first.')}\n </p>\n <div className=\"flex justify-end gap-3\">\n <Button type=\"button\" variant=\"outline\" onClick={handleVectorReindexCancel}>\n {t('search.settings.actions.cancel', 'Cancel')}\n </Button>\n <Button type=\"button\" variant=\"default\" onClick={handleVectorReindexConfirm}>\n {t('search.settings.reindex.confirmButton', 'Start Reindex')}\n </Button>\n </div>\n </div>\n </div>\n )}\n\n {/* Embedding Provider Change Confirmation Dialog */}\n {showEmbeddingConfirmDialog && pendingEmbeddingConfig && (\n <div className=\"fixed inset-0 z-50 flex items-center justify-center bg-black/50\">\n <div className=\"mx-4 max-w-lg rounded-lg border border-border bg-card p-6 shadow-lg\">\n <h3 className=\"text-lg font-semibold mb-2\">{t('search.settings.change.title', 'Confirm Provider Change')}</h3>\n <p className=\"text-sm text-muted-foreground mb-4\">\n {t('search.settings.change.description', 'Changing the embedding provider will require reindexing all data.')}\n </p>\n <div className=\"mb-4 p-3 rounded-md bg-muted/50 text-sm\">\n <p className=\"font-medium\">\n {embeddingSettings?.embeddingConfig\n ? `${EMBEDDING_PROVIDERS[embeddingSettings.embeddingConfig.providerId].name} (${embeddingSettings.embeddingConfig.model})`\n : 'Default'}\n {' \u2192 '}\n {EMBEDDING_PROVIDERS[pendingEmbeddingConfig.providerId].name} ({pendingEmbeddingConfig.model})\n </p>\n <p className=\"text-muted-foreground\">\n {embeddingSettings?.indexedDimension ?? 'N/A'} \u2192 {pendingEmbeddingConfig.dimension} dimensions\n </p>\n </div>\n <ul className=\"mb-4 space-y-1 text-sm\">\n <li className=\"flex items-start gap-2\">\n <span className=\"text-destructive\">\u2022</span>\n <span>{t('search.settings.change.bullet1', 'Existing vector data will be cleared')}</span>\n </li>\n <li className=\"flex items-start gap-2\">\n <span className=\"text-destructive\">\u2022</span>\n <span>{t('search.settings.change.bullet2', 'Vector search will be unavailable during reindex')}</span>\n </li>\n </ul>\n <div className=\"flex justify-end gap-3\">\n <Button type=\"button\" variant=\"outline\" onClick={handleEmbeddingCancelChange} disabled={embeddingSaving}>\n {t('search.settings.actions.cancel', 'Cancel')}\n </Button>\n <Button type=\"button\" variant=\"destructive\" onClick={handleEmbeddingConfirmChange} disabled={embeddingSaving}>\n {embeddingSaving ? <Spinner size=\"sm\" className=\"mr-2\" /> : null}\n {t('search.settings.actions.confirm', 'Confirm')}\n </Button>\n </div>\n </div>\n </div>\n )}\n </div>\n )\n}\n\nexport default VectorSearchSection\n"],
5
+ "mappings": ";AAsbM,SAoWc,UApWd,KAQE,YARF;AApbN,YAAY,WAAW;AACvB,SAAS,YAAY;AACrB,SAAS,4BAA4B;AACrC,SAAS,aAAa;AACtB,SAAS,cAAc;AACvB,SAAS,aAAa;AACtB,SAAS,eAAe;AACxB,SAAS,MAAM,UAAU,aAAa,mBAAmB;AAwFzD,MAAM,sBAA0E;AAAA,EAC9E,QAAQ;AAAA,IACN,MAAM;AAAA,IACN,gBAAgB;AAAA,IAChB,cAAc;AAAA,IACd,QAAQ;AAAA,MACN,EAAE,IAAI,0BAA0B,MAAM,0BAA0B,WAAW,KAAK;AAAA,MAChF,EAAE,IAAI,0BAA0B,MAAM,0BAA0B,WAAW,MAAM,uBAAuB,MAAM,cAAc,KAAK,cAAc,KAAK;AAAA,MACpJ,EAAE,IAAI,0BAA0B,MAAM,0BAA0B,WAAW,KAAK;AAAA,IAClF;AAAA,EACF;AAAA,EACA,QAAQ;AAAA,IACN,MAAM;AAAA,IACN,gBAAgB;AAAA,IAChB,cAAc;AAAA,IACd,QAAQ;AAAA,MACN,EAAE,IAAI,sBAAsB,MAAM,sBAAsB,WAAW,KAAK,uBAAuB,MAAM,cAAc,GAAG,cAAc,IAAI;AAAA,MACxI,EAAE,IAAI,iBAAiB,MAAM,iBAAiB,WAAW,IAAI;AAAA,IAC/D;AAAA,EACF;AAAA,EACA,SAAS;AAAA,IACP,MAAM;AAAA,IACN,gBAAgB;AAAA,IAChB,cAAc;AAAA,IACd,QAAQ;AAAA,MACN,EAAE,IAAI,iBAAiB,MAAM,iBAAiB,WAAW,KAAK;AAAA,IAChE;AAAA,EACF;AAAA,EACA,QAAQ;AAAA,IACN,MAAM;AAAA,IACN,gBAAgB;AAAA,IAChB,cAAc;AAAA,IACd,QAAQ;AAAA,MACN,EAAE,IAAI,sBAAsB,MAAM,sBAAsB,WAAW,KAAK;AAAA,MACxE,EAAE,IAAI,2BAA2B,MAAM,2BAA2B,WAAW,KAAK;AAAA,MAClF,EAAE,IAAI,4BAA4B,MAAM,4BAA4B,WAAW,IAAI;AAAA,MACnF,EAAE,IAAI,iCAAiC,MAAM,iCAAiC,WAAW,IAAI;AAAA,IAC/F;AAAA,EACF;AAAA,EACA,SAAS;AAAA,IACP,MAAM;AAAA,IACN,gBAAgB;AAAA,IAChB,cAAc;AAAA,IACd,QAAQ;AAAA,MACN,EAAE,IAAI,gCAAgC,MAAM,uBAAuB,WAAW,MAAM,uBAAuB,MAAM,cAAc,KAAK,cAAc,KAAK;AAAA,MACvJ,EAAE,IAAI,8BAA8B,MAAM,uBAAuB,WAAW,KAAK;AAAA,MACjF,EAAE,IAAI,2BAA2B,MAAM,2BAA2B,WAAW,KAAK;AAAA,MAClF,EAAE,IAAI,gCAAgC,MAAM,gCAAgC,WAAW,KAAK;AAAA,IAC9F;AAAA,EACF;AAAA,EACA,QAAQ;AAAA,IACN,MAAM;AAAA,IACN,gBAAgB;AAAA,IAChB,cAAc;AAAA,IACd,QAAQ;AAAA,MACN,EAAE,IAAI,oBAAoB,MAAM,oBAAoB,WAAW,IAAI;AAAA,MACnE,EAAE,IAAI,qBAAqB,MAAM,qBAAqB,WAAW,KAAK;AAAA,MACtE,EAAE,IAAI,cAAc,MAAM,cAAc,WAAW,IAAI;AAAA,MACvD,EAAE,IAAI,0BAA0B,MAAM,0BAA0B,WAAW,KAAK;AAAA,IAClF;AAAA,EACF;AACF;AAYO,SAAS,oBAAoB;AAAA,EAClC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAA6B;AAC3B,QAAM,IAAI,KAAK;AACf,QAAM,CAAC,iBAAiB,kBAAkB,IAAI,MAAM,SAAS,KAAK;AAClE,QAAM,0BAA0B,MAAM,OAAgB,IAAI;AAG1D,QAAM,CAAC,kBAAkB,mBAAmB,IAAI,MAAM,SAAqC,IAAI;AAC/F,QAAM,CAAC,eAAe,gBAAgB,IAAI,MAAM,SAAwB,IAAI;AAC5E,QAAM,CAAC,iBAAiB,kBAAkB,IAAI,MAAM,SAAiB,EAAE;AACvE,QAAM,CAAC,iBAAiB,kBAAkB,IAAI,MAAM,SAAiB,GAAG;AAExE,QAAM,CAAC,wBAAwB,yBAAyB,IAAI,MAAM,SAAyC,IAAI;AAC/G,QAAM,CAAC,4BAA4B,6BAA6B,IAAI,MAAM,SAAS,KAAK;AAGxF,QAAM,CAAC,kBAAkB,mBAAmB,IAAI,MAAM,SAAS,KAAK;AACpE,QAAM,CAAC,yBAAyB,0BAA0B,IAAI,MAAM,SAAS,KAAK;AAGlF,QAAM,CAAC,cAAc,eAAe,IAAI,MAAM,SAAwB,CAAC,CAAC;AACxE,QAAM,CAAC,iBAAiB,kBAAkB,IAAI,MAAM,SAAS,IAAI;AAGjE,QAAM,oBAAoB,MAAM,YAAY,YAAY;AACtD,uBAAmB,IAAI;AACvB,QAAI;AACF,YAAM,WAAW,MAAM,MAAM,yBAAyB;AACtD,UAAI,SAAS,IAAI;AACf,cAAM,OAAO,MAAM,SAAS,KAAK;AACjC,cAAM,UAAyB,CAAC;AAChC,YAAI,KAAK,MAAM;AACb,kBAAQ,KAAK,GAAG,KAAK,IAAI;AAAA,QAC3B;AACA,YAAI,KAAK,QAAQ;AACf,kBAAQ,KAAK,GAAG,KAAK,OAAO,IAAI,UAAQ,EAAE,GAAG,KAAK,OAAO,QAAiB,EAAE,CAAC;AAAA,QAC/E;AAEA,cAAM,aAAa,QAAQ,OAAO,SAAO;AACvC,gBAAM,cAAc,IAAI,QAAQ,YAAY,KAAK;AACjD,gBAAM,eAAe,IAAI,SAAS,YAAY,KAAK;AACnD,gBAAM,eAAe,IAAI,SAAS,YAAY,KAAK;AACnD,iBAAO,YAAY,SAAS,QAAQ,KAAK,aAAa,SAAS,QAAQ,KACrE,aAAa,SAAS,WAAW,KAAK,aAAa,SAAS,QAAQ;AAAA,QACxE,CAAC;AACD,mBAAW,KAAK,CAAC,GAAG,MAAM,IAAI,KAAK,EAAE,UAAU,EAAE,QAAQ,IAAI,IAAI,KAAK,EAAE,UAAU,EAAE,QAAQ,CAAC;AAC7F,wBAAgB,WAAW,MAAM,GAAG,EAAE,CAAC;AAAA,MACzC;AAAA,IACF,QAAQ;AAAA,IAER,UAAE;AACA,yBAAmB,KAAK;AAAA,IAC1B;AAAA,EACF,GAAG,CAAC,CAAC;AAEL,QAAM,UAAU,MAAM;AACpB,sBAAkB;AAAA,EACpB,GAAG,CAAC,iBAAiB,CAAC;AAGtB,QAAM,UAAU,MAAM;AACpB,QAAI,qBAAqB,kBAAkB;AACzC,YAAM,WAAW,YAAY,mBAAmB,GAAI;AACpD,aAAO,MAAM,cAAc,QAAQ;AAAA,IACrC;AAAA,EACF,GAAG,CAAC,mBAAmB,kBAAkB,iBAAiB,CAAC;AAG3D,QAAM,qBAAqB,MAAM,YAAY,OAAO,cAAuB;AACzE,4BAAwB,UAAU,mBAAmB,uBAAuB;AAC5E,QAAI,mBAAmB;AACrB,gCAA0B,EAAE,GAAG,mBAAmB,qBAAqB,UAAU,CAAC;AAAA,IACpF;AACA,uBAAmB,IAAI;AACvB,QAAI;AACF,YAAM,OAAO,MAAM;AAAA,QACjB;AAAA,QACA;AAAA,UACE,QAAQ;AAAA,UACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,UAC9C,MAAM,KAAK,UAAU,EAAE,qBAAqB,UAAU,CAAC;AAAA,QACzD;AAAA,QACA,EAAE,cAAc,EAAE,qCAAqC,yBAAyB,GAAG,iBAAiB,KAAK;AAAA,MAC3G;AACA,UAAI,MAAM,UAAU;AAClB,kCAA0B,KAAK,QAAQ;AACvC,gCAAwB,UAAU,KAAK,SAAS;AAAA,MAClD;AACA,YAAM,EAAE,kCAAkC,gBAAgB,GAAG,SAAS;AAAA,IACxE,QAAQ;AACN,UAAI,mBAAmB;AACrB,kCAA0B,EAAE,GAAG,mBAAmB,qBAAqB,wBAAwB,QAAQ,CAAC;AAAA,MAC1G;AAAA,IACF,UAAE;AACA,yBAAmB,KAAK;AAAA,IAC1B;AAAA,EACF,GAAG,CAAC,mBAAmB,2BAA2B,CAAC,CAAC;AAGpD,QAAM,uBAAuB,CAAC,eAAoC;AAChE,wBAAoB,UAAU;AAC9B,qBAAiB,IAAI;AACrB,uBAAmB,EAAE;AACrB,uBAAmB,GAAG;AAAA,EACxB;AAEA,QAAM,oBAAoB,CAAC,YAAoB;AAC7C,qBAAiB,OAAO;AAAA,EAC1B;AAEA,QAAM,8BAA8B,MAAM;AACxC,UAAM,gBAAgB,oBAAoB,mBAAmB,iBAAiB,cAAc;AAC5F,UAAM,kBAAkB,oBAAoB,aAAa;AACzD,UAAM,aAAa,kBAAkB,mBAAmB,gBAAgB,eAAe,mBAAmB,iBAAiB,SAAS,gBAAgB;AAEpJ,QAAI;AACJ,QAAI;AAEJ,QAAI,eAAe,UAAU;AAC3B,kBAAY,gBAAgB,KAAK;AACjC,kBAAY;AACZ,UAAI,CAAC,WAAW;AACd,cAAM,EAAE,wCAAwC,2BAA2B,GAAG,OAAO;AACrF;AAAA,MACF;AACA,UAAI,aAAa,GAAG;AAClB,cAAM,EAAE,4CAA4C,gCAAgC,GAAG,OAAO;AAC9F;AAAA,MACF;AAAA,IACF,OAAO;AACL,YAAM,WAAW,gBAAgB,OAAO,KAAK,CAAC,MAAM,EAAE,OAAO,UAAU,KAAK,gBAAgB,OAAO,CAAC;AACpG,kBAAY,SAAS;AACrB,kBAAY,SAAS;AAAA,IACvB;AAEA,UAAM,YAAqC;AAAA,MACzC,YAAY;AAAA,MACZ,OAAO;AAAA,MACP;AAAA,MACA,YAAW,oBAAI,KAAK,GAAE,YAAY;AAAA,IACpC;AAEA,QAAI,mBAAmB,oBAAoB,mBAAmB,iBAAiB;AAC7E,gCAA0B,SAAS;AACnC,oCAA8B,IAAI;AAAA,IACpC,OAAO;AACL,2BAAqB,SAAS;AAAA,IAChC;AAAA,EACF;AAEA,QAAM,iCAAiC,MAAM;AAC3C,wBAAoB,IAAI;AACxB,qBAAiB,IAAI;AACrB,uBAAmB,EAAE;AACrB,uBAAmB,GAAG;AAAA,EACxB;AAEA,QAAM,uBAAuB,OAAO,WAAoC;AACtE,uBAAmB,IAAI;AACvB,kCAA8B,KAAK;AACnC,8BAA0B,IAAI;AAE9B,QAAI;AACF,YAAM;AAAA,QACJ;AAAA,QACA;AAAA,UACE,QAAQ;AAAA,UACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,UAC9C,MAAM,KAAK,UAAU,EAAE,iBAAiB,OAAO,CAAC;AAAA,QAClD;AAAA,QACA,EAAE,cAAc,EAAE,qCAAqC,yBAAyB,GAAG,iBAAiB,KAAK;AAAA,MAC3G;AACA,0BAAoB,IAAI;AACxB,uBAAiB,IAAI;AACrB,YAAM,EAAE,0CAA0C,0BAA0B,GAAG,SAAS;AACxF,YAAM,oBAAoB;AAAA,IAC5B,QAAQ;AAAA,IAER,UAAE;AACA,yBAAmB,KAAK;AAAA,IAC1B;AAAA,EACF;AAEA,QAAM,+BAA+B,MAAM;AACzC,QAAI,wBAAwB;AAC1B,2BAAqB,sBAAsB;AAAA,IAC7C;AAAA,EACF;AAEA,QAAM,8BAA8B,MAAM;AACxC,kCAA8B,KAAK;AACnC,8BAA0B,IAAI;AAAA,EAChC;AAGA,QAAM,2BAA2B,MAAM;AACrC,+BAA2B,IAAI;AAAA,EACjC;AAEA,QAAM,6BAA6B,YAAY;AAC7C,+BAA2B,KAAK;AAChC,wBAAoB,IAAI;AACxB,QAAI;AACF,YAAM;AAAA,QACJ;AAAA,QACA;AAAA,UACE,QAAQ;AAAA,UACR,SAAS,EAAE,gBAAgB,mBAAmB;AAAA,UAC9C,MAAM,KAAK,UAAU,EAAE,YAAY,KAAK,CAAC;AAAA,QAC3C;AAAA,QACA,EAAE,cAAc,EAAE,wCAAwC,gBAAgB,GAAG,iBAAiB,KAAK;AAAA,MACrG;AACA,YAAM,EAAE,2CAA2C,iBAAiB,GAAG,SAAS;AAChF,YAAM,kBAAkB;AAAA,IAC1B,QAAQ;AAAA,IAER,UAAE;AACA,0BAAoB,KAAK;AAAA,IAC3B;AAAA,EACF;AAEA,QAAM,4BAA4B,MAAM;AACtC,+BAA2B,KAAK;AAAA,EAClC;AAGA,QAAM,gBAAgB,mBAAmB,iBAAiB,cAAc;AACxE,QAAM,oBAAoB,oBAAoB,aAAa;AAC3D,QAAM,aAAa,mBAAmB,iBAAiB,SAAS,kBAAkB;AAClF,QAAM,iBAAiB,mBAAmB,iBAAiB,aAAa,kBAAkB,OAAO,CAAC,GAAG,aAAa;AAElH,QAAM,yBAAyB,kBAAkB,OAAO,KAAK,CAAC,MAAM,EAAE,OAAO,UAAU;AACvF,QAAM,mBAAmB,CAAC,0BAA0B,aAAa,EAAE,IAAI,YAAY,MAAM,YAAY,WAAW,eAAe,IAAI;AAEnI,QAAM,kBAAkB,oBAAoB;AAC5C,QAAM,sBAAsB,oBAAoB,eAAe;AAC/D,QAAM,eAAe,kBAAkB,mBAAmB,oBAAoB,eAAe;AAC7F,QAAM,gBAAgB,iBAAiB;AAEvC,QAAM,4BAA4B,CAAC,iBAAiB,oBAAoB,iBAAiB,oBAAoB,iBAAiB,iBAAiB;AAE/I,QAAM,mBAAmB,gBACrB,OACA,4BACE,mBACA,oBAAoB,OAAO,KAAK,CAAC,MAAM,EAAE,OAAO,YAAY,KAAK,oBAAoB,OAAO,CAAC;AACnG,QAAM,mBAAmB,gBAAgB,kBAAmB,kBAAkB,aAAa;AAE3F,QAAM,6BAA8B,qBAAqB,QAAQ,qBAAqB,iBACnF,kBAAkB,QAAQ,kBAAkB,cAC5C,qBAAqB,QAAQ,kBAAkB,QAAQ,oBAAoB,iBAAiB,cAC5F,kBAAkB,gBAAgB,KAAK,MAAM,MAAM,oBAAoB;AAE1E,QAAM,wBAAwB,mBAAmB,qBAAqB,SAAS,aAAa;AAC5F,QAAM,kBAAyC,CAAC,UAAU,UAAU,WAAW,UAAU,WAAW,QAAQ;AAE5G,QAAM,sBAAsB,oBAAoB,kBAAkB,sBAAsB;AACxF,QAAM,uBAAuB,oBAAoB,mBAAmB,QAAQ,mBAAmB,kBAAkB;AAEjH,SACE,qBAAC,SAAI,WAAU,yDACb;AAAA,wBAAC,QAAG,WAAU,8BACX,YAAE,uCAAuC,eAAe,GAC3D;AAAA,IACA,oBAAC,OAAE,WAAU,sCACV,YAAE,6CAA6C,8CAA8C,GAChG;AAAA,IAEA,qBAAC,QAAK,cAAa,iBACjB;AAAA,2BAAC,YAAS,WAAU,QAClB;AAAA,4BAAC,eAAY,OAAM,iBAChB,YAAE,sCAAsC,eAAe,GAC1D;AAAA,QACA,oBAAC,eAAY,OAAM,SAChB,YAAE,wCAAwC,kBAAkB,GAC/D;AAAA,QACA,oBAAC,eAAY,OAAM,YAChB,YAAE,iCAAiC,UAAU,GAChD;AAAA,SACF;AAAA,MAGA,oBAAC,eAAY,OAAM,iBACf,8BAAoB,2BACpB,qBAAC,SAAI,WAAU,iDACb;AAAA,4BAAC,WAAQ,MAAK,MAAK;AAAA,QACnB,oBAAC,UAAM,YAAE,gCAAgC,qBAAqB,GAAE;AAAA,SAClE,IAEA,qBAAC,SAAI,WAAU,aAEb;AAAA,6BAAC,SACC;AAAA,8BAAC,QAAG,WAAU,8BAA8B,YAAE,gCAAgC,cAAc,GAAE;AAAA,UAC9F,oBAAC,SAAI,WAAU,6BACZ,6BAAmB,QAAQ,IAAI,CAAC,WAAW;AAC1C,kBAAM,YAAY,OAAO,OAAO,kBAAkB;AAClD,kBAAM,UAAU,OAAO,cAAc,OAAO;AAC5C,mBACE;AAAA,cAAC;AAAA;AAAA,gBAEC,WAAW,gDACT,aAAa,UACT,oFACA,CAAC,OAAO,cACN,yCACA,2BACR;AAAA,gBAEA;AAAA,sCAAC,SAAI,WAAW,uEACd,aAAa,UACT,iFACA,gCACN,IACE,8BAAC,SAAI,WAAU,WAAU,SAAQ,aAAY,MAAK,QAAO,QAAO,gBAAe,aAAa,GAC1F,8BAAC,UAAK,eAAc,SAAQ,gBAAe,SAAQ,GAAE,kJAAiJ,GACxM,GACF;AAAA,kBACA,qBAAC,SAAI,WAAU,kBACb;AAAA,yCAAC,SAAI,WAAU,2BACb;AAAA,0CAAC,OAAE,WAAW,uBAAuB,aAAa,UAAU,2CAA2C,EAAE,IACtG,iBAAO,MACV;AAAA,sBACC,aACC,oBAAC,UAAK,WAAU,kHACb,YAAE,iCAAiC,QAAQ,GAC9C;AAAA,sBAED,CAAC,OAAO,eACP,oBAAC,UAAK,WAAU,oEACb,YAAE,qCAAqC,aAAa,GACvD;AAAA,uBAEJ;AAAA,oBACA,oBAAC,SAAI,WAAU,oBACZ,iBAAO,QAAQ,IAAI,CAAC,WACnB,qBAAC,SAAsB,WAAU,6BAC/B;AAAA,0CAAC,SAAI,WAAW,4BAA4B,OAAO,MAAM,mBAAmB,wBAAwB,IAAI;AAAA,sBACxG,oBAAC,UAAK,WAAU,+CAA+C,iBAAO,MAAK;AAAA,yBAFnE,OAAO,IAGjB,CACD,GACH;AAAA,qBACF;AAAA;AAAA;AAAA,cA1CK,OAAO;AAAA,YA2Cd;AAAA,UAEJ,CAAC,GACH;AAAA,WACF;AAAA,QAGA,qBAAC,SACC;AAAA,8BAAC,QAAG,WAAU,8BAA8B,YAAE,oCAAoC,oBAAoB,GAAE;AAAA,UACxG,oBAAC,OAAE,WAAU,sCAAsC,YAAE,wCAAwC,oGAAoG,GAAE;AAAA,UACnM,oBAAC,SAAI,WAAU,wDACZ,0BAAgB,IAAI,CAAC,eAAe;AACnC,kBAAM,OAAO,oBAAoB,UAAU;AAC3C,kBAAM,eAAe,mBAAmB,qBAAqB,SAAS,UAAU;AAChF,kBAAM,aAAa,oBAAoB;AACvC,kBAAM,mBAAmB,kBAAkB;AAC3C,mBACE;AAAA,cAAC;AAAA;AAAA,gBAEC,MAAK;AAAA,gBACL,SAAS,MAAM,gBAAgB,qBAAqB,UAAU;AAAA,gBAC9D,UAAU,CAAC,gBAAgB,oBAAoB;AAAA,gBAC/C,WAAW,oDACT,aACI,uDACA,eACE,2EACA,yDACR;AAAA,gBAEA;AAAA,uCAAC,SAAI,WAAU,0CACb;AAAA,yCAAC,SAAI,WAAU,kBACb;AAAA,2CAAC,SAAI,WAAU,2BACb;AAAA,4CAAC,OAAE,WAAW,uBAAuB,aAAa,iBAAiB,eAAe,KAAK,uBAAuB,IAC3G,eAAK,MACR;AAAA,wBACC,oBAAoB,gBACnB,oBAAC,UAAK,WAAU,kHACb,YAAE,iCAAiC,QAAQ,GAC9C;AAAA,yBAEJ;AAAA,sBACC,eACC,qBAAC,OAAE,WAAU,sCACV;AAAA,6BAAK,OAAO;AAAA,wBAAO;AAAA,wBAAE,EAAE,0CAA0C,kBAAkB;AAAA,yBACtF,IAEA,qBAAC,OAAE,WAAU,sCACV;AAAA,0BAAE,oCAAoC,KAAK;AAAA,wBAAE;AAAA,wBAAC,oBAAC,UAAK,WAAU,+CAA+C,eAAK,gBAAe;AAAA,yBACpI;AAAA,uBAEJ;AAAA,oBACA,oBAAC,SAAI,WAAW,uEACd,aACI,uCACA,eACE,iFACA,gCACR,IACG,uBACC,oBAAC,SAAI,WAAU,WAAU,MAAK,QAAO,SAAQ,aAAY,QAAO,gBAAe,aAAa,GAC1F,8BAAC,UAAK,eAAc,SAAQ,gBAAe,SAAQ,GAAE,kBAAiB,GACxE,IACE,eACF,oBAAC,SAAI,WAAU,WAAU,MAAK,QAAO,SAAQ,aAAY,QAAO,gBAAe,aAAa,GAC1F,8BAAC,UAAK,eAAc,SAAQ,gBAAe,SAAQ,GAAE,kBAAiB,GACxE,IAEA,oBAAC,SAAI,WAAU,WAAU,MAAK,QAAO,SAAQ,aAAY,QAAO,gBAAe,aAAa,GAC1F,8BAAC,UAAK,eAAc,SAAQ,gBAAe,SAAQ,GAAE,kBAAiB,GACxE,GAEJ;AAAA,qBACF;AAAA,kBAGC,cAAc,gBACb,qBAAC,SAAI,WAAU,8CAA6C,SAAS,CAAC,MAAM,EAAE,gBAAgB,GAC5F;AAAA,yCAAC,SAAI,WAAU,aACb;AAAA,0CAAC,SAAM,SAAS,SAAS,UAAU,IAAI,WAAU,uBAC9C,YAAE,+BAA+B,OAAO,GAC3C;AAAA,sBACA;AAAA,wBAAC;AAAA;AAAA,0BACC,IAAI,SAAS,UAAU;AAAA,0BACvB,WAAU;AAAA,0BACV,OAAO;AAAA,0BACP,UAAU,CAAC,MAAM,kBAAkB,EAAE,OAAO,KAAK;AAAA,0BACjD,UAAU,oBAAoB;AAAA,0BAE7B;AAAA,gDAAoB,oBAAoB,iBACvC,qBAAC,YAAiC,OAAO,iBAAiB,IACvD;AAAA,+CAAiB;AAAA,8BAAK;AAAA,8BAAG,iBAAiB;AAAA,8BAAU;AAAA,iCAD1C,iBAAiB,EAE9B;AAAA,4BAED,oBAAoB,OAAO,IAAI,CAAC,UAC/B,qBAAC,YAAsB,OAAO,MAAM,IACjC;AAAA,oCAAM;AAAA,8BAAK;AAAA,8BAAG,MAAM;AAAA,8BAAU;AAAA,iCADpB,MAAM,EAEnB,CACD;AAAA,4BACD,oBAAC,YAAO,OAAM,UAAU,YAAE,gCAAgC,WAAW,GAAE;AAAA;AAAA;AAAA,sBACzE;AAAA,uBACF;AAAA,oBAEC,iBACC,qBAAC,SAAI,WAAU,yDACb;AAAA;AAAA,wBAAC;AAAA;AAAA,0BACC,MAAK;AAAA,0BACL,WAAU;AAAA,0BACV,OAAO;AAAA,0BACP,UAAU,CAAC,MAAM,mBAAmB,EAAE,OAAO,KAAK;AAAA,0BAClD,aAAa,EAAE,yCAAyC,YAAY;AAAA,0BACpE,UAAU,oBAAoB;AAAA;AAAA,sBAChC;AAAA,sBACA;AAAA,wBAAC;AAAA;AAAA,0BACC,MAAK;AAAA,0BACL,WAAU;AAAA,0BACV,OAAO;AAAA,0BACP,UAAU,CAAC,MAAM,mBAAmB,OAAO,EAAE,OAAO,KAAK,KAAK,GAAG;AAAA,0BACjE,aAAY;AAAA,0BACZ,KAAK;AAAA,0BACL,UAAU,oBAAoB;AAAA;AAAA,sBAChC;AAAA,uBACF;AAAA,oBAGF,qBAAC,SAAI,WAAU,6CACb;AAAA,2CAAC,UAAK,WAAU,yBACb;AAAA,0BAAE,mCAAmC,YAAY;AAAA,wBAAE;AAAA,wBAAG;AAAA,yBACzD;AAAA,sBACC,mBAAmB,oBAAoB,kBAAkB,qBAAqB,oBAC7E,qBAAC,UAAK,WAAU,sCACb;AAAA,0BAAE,sCAAsC,UAAU;AAAA,wBAAE;AAAA,wBAAG,kBAAkB;AAAA,yBAC5E;AAAA,uBAEJ;AAAA,oBAEC,8BACC,qBAAC,SAAI,WAAU,mBACb;AAAA,2CAAC,UAAO,MAAK,UAAS,SAAQ,WAAU,MAAK,MAAK,WAAU,UAAS,SAAS,6BAA6B,UAAU,oBAAoB,iBACtI;AAAA,0CAAkB,oBAAC,WAAQ,MAAK,MAAK,WAAU,QAAO,IAAK;AAAA,wBAC3D,EAAE,iCAAiC,OAAO;AAAA,yBAC7C;AAAA,sBACA,oBAAC,UAAO,MAAK,UAAS,SAAQ,WAAU,MAAK,MAAK,SAAS,gCAAgC,UAAU,oBAAoB,iBACtH,YAAE,kCAAkC,QAAQ,GAC/C;AAAA,uBACF;AAAA,qBAEJ;AAAA;AAAA;AAAA,cAjIG;AAAA,YAmIP;AAAA,UAEJ,CAAC,GACH;AAAA,WACF;AAAA,QAGA,oBAAC,SAAI,WAAU,6FACb,+BAAC,SAAI,WAAU,0BACb;AAAA,8BAAC,SAAI,WAAU,iEAAgE,MAAK,QAAO,SAAQ,aAAY,QAAO,gBACpH,8BAAC,UAAK,eAAc,SAAQ,gBAAe,SAAQ,aAAa,GAAG,GAAE,6DAA4D,GACnI;AAAA,UACA,qBAAC,SAAI,WAAU,4CACb;AAAA,gCAAC,OAAE,WAAU,oBAAoB,YAAE,gCAAgC,eAAe,GAAE;AAAA,YACpF,oBAAC,OAAE,WAAU,WAAW,YAAE,2CAA2C,yHAAyH,GAAE;AAAA,aAClM;AAAA,WACF,GACF;AAAA,SACF,GAEJ;AAAA,MAGA,oBAAC,eAAY,OAAM,SAChB,6BACC,qBAAC,SAAI,WAAU,iDACb;AAAA,4BAAC,WAAQ,MAAK,MAAK;AAAA,QACnB,oBAAC,UAAM,YAAE,gCAAgC,qBAAqB,GAAE;AAAA,SAClE,IACE,CAAC,wBACH,oBAAC,SAAI,WAAU,iGACb,+BAAC,SAAI,WAAU,0BACb;AAAA,4BAAC,SAAI,WAAU,mEAAkE,MAAK,QAAO,SAAQ,aAAY,QAAO,gBACtH,8BAAC,UAAK,eAAc,SAAQ,gBAAe,SAAQ,aAAa,GAAG,GAAE,wIAAuI,GAC9M;AAAA,QACA,qBAAC,SACC;AAAA,8BAAC,OAAE,WAAU,0DACV,YAAE,uCAAuC,kCAAkC,GAC9E;AAAA,UACA,oBAAC,OAAE,WAAU,mDACV,YAAE,2CAA2C,8EAA8E,GAC9H;AAAA,WACF;AAAA,SACF,GACF,IAEA,qBAAC,SAAI,WAAU,aAEZ;AAAA,2BAAmB,kBAAkB,QAAQ,mBAAmB,kBAAkB,UACjF,qBAAC,SAAI,WAAU,gDACb;AAAA,8BAAC,OAAE,WAAU,iCAAiC,YAAE,wCAAwC,YAAY,GAAE;AAAA,UACtG,oBAAC,OAAE,WAAU,sBAAsB,4BAAkB,cAAc,eAAe,GAAE;AAAA,WACtF;AAAA,QAIF,oBAAC,SAAI,WAAU,8DACb,+BAAC,SAAI,WAAU,UACb;AAAA,+BAAC,SAAI,WAAU,2BACb;AAAA;AAAA,cAAC;AAAA;AAAA,gBACC,IAAG;AAAA,gBACH,MAAK;AAAA,gBACL,WAAU;AAAA,gBACV,SAAS;AAAA,gBACT,UAAU,CAAC,UAAU,mBAAmB,MAAM,OAAO,OAAO;AAAA,gBAC5D,UAAU;AAAA;AAAA,YACZ;AAAA,YACA,oBAAC,SAAM,SAAQ,wBAAuB,WAAU,uBAC7C,YAAE,sCAAsC,sBAAsB,GACjE;AAAA,YACC,kBAAkB,oBAAC,WAAQ,MAAK,MAAK,WAAU,yBAAwB,IAAK;AAAA,aAC/E;AAAA,UACA,oBAAC,OAAE,WAAU,2CACV,YAAE,4CAA4C,gEAAgE,GACjH;AAAA,UACC,mBAAmB,sBAClB,oBAAC,OAAE,WAAU,sCACV,YAAE,uCAAuC,oCAAoC,GAChF;AAAA,WAEJ,GACF;AAAA,QAGA,qBAAC,SAAI,WAAU,aACb;AAAA,8BAAC,QAAG,WAAU,yBAAyB,YAAE,uCAAuC,cAAc,GAAE;AAAA,UAChG,oBAAC,OAAE,WAAU,iCACV,YAAE,6CAA6C,kHAAkH,GACpK;AAAA,UAGC,qBACC,oBAAC,SAAI,WAAU,6FACb,+BAAC,SAAI,WAAU,0BACb;AAAA,gCAAC,WAAQ,MAAK,MAAK,WAAU,yDAAwD;AAAA,YACrF,qBAAC,SAAI,WAAU,UACb;AAAA,kCAAC,OAAE,WAAU,wDACV,YAAE,qCAAqC,+BAA+B,GACzE;AAAA,cACA,oBAAC,OAAE,WAAU,iDACV,YAAE,4CAA4C,wDAAwD;AAAA,gBACrG,QAAQ,kBAAkB;AAAA,gBAC1B,SAAS,kBAAkB;AAAA,cAC7B,CAAC,GACH;AAAA,eACF;AAAA,aACF,GACF;AAAA,UAGF,qBAAC,SAAI,WAAU,wEACb;AAAA,gCAAC,SAAI,WAAU,4DAA2D,MAAK,QAAO,SAAQ,aAAY,QAAO,gBAC/G,8BAAC,UAAK,eAAc,SAAQ,gBAAe,SAAQ,aAAa,GAAG,GAAE,wIAAuI,GAC9M;AAAA,YACA,oBAAC,OAAE,WAAU,8CACV,YAAE,yCAAyC,wEAAwE,GACtH;AAAA,aACF;AAAA,UACA;AAAA,YAAC;AAAA;AAAA,cACC,MAAK;AAAA,cACL,SAAQ;AAAA,cACR,MAAK;AAAA,cACL,SAAS;AAAA,cACT,UAAU,oBAAoB,mBAAmB,oBAAoB,sBAAsB;AAAA,cAE1F,8BAAoB,sBAAsB,OACzC,iCACE;AAAA,oCAAC,WAAQ,MAAK,MAAK,WAAU,QAAO;AAAA,gBACnC,EAAE,yCAAyC,eAAe;AAAA,iBAC7D,IAEA,EAAE,wCAAwC,cAAc;AAAA;AAAA,UAE5D;AAAA,WACF;AAAA,SACF,GAEJ;AAAA,MAGA,qBAAC,eAAY,OAAM,YAChB;AAAA,0BACC,qBAAC,SAAI,WAAU,iDACb;AAAA,8BAAC,WAAQ,MAAK,MAAK;AAAA,UACnB,oBAAC,UAAM,YAAE,gCAAgC,YAAY,GAAE;AAAA,WACzD,IACE,aAAa,WAAW,IAC1B,oBAAC,SAAI,WAAU,0CACb,8BAAC,OAAE,WAAU,iCACV,YAAE,mCAAmC,6BAA6B,GACrE,GACF,IAEA,oBAAC,SAAI,WAAU,sCACZ,uBAAa,IAAI,CAAC,QACjB;AAAA,UAAC;AAAA;AAAA,YAEC,WAAW,0BACT,IAAI,UAAU,UACV,2EACA,aACN;AAAA,YAEA,+BAAC,SAAI,WAAU,0BACZ;AAAA,kBAAI,UAAU,WACb,oBAAC,SAAI,WAAU,+DAA8D,MAAK,QAAO,SAAQ,aAAY,QAAO,gBAClH,8BAAC,UAAK,eAAc,SAAQ,gBAAe,SAAQ,aAAa,GAAG,GAAE,qDAAoD,GAC3H;AAAA,cAEF,qBAAC,SAAI,WAAU,kBACb;AAAA,oCAAC,OAAE,WAAW,WAAW,IAAI,UAAU,UAAU,mCAAmC,iBAAiB,IAClG,cAAI,SACP;AAAA,gBACA,qBAAC,OAAE,WAAU,wCACT;AAAA,yBAAM;AACN,0BAAM,IAAI,IAAI,KAAK,IAAI,UAAU;AACjC,0BAAM,MAAM,CAAC,MAAc,EAAE,SAAS,EAAE,SAAS,GAAG,GAAG;AACvD,2BAAO,GAAG,IAAI,EAAE,QAAQ,CAAC,CAAC,IAAI,IAAI,EAAE,SAAS,IAAI,CAAC,CAAC,IAAI,EAAE,YAAY,CAAC,IAAI,IAAI,EAAE,SAAS,CAAC,CAAC,IAAI,IAAI,EAAE,WAAW,CAAC,CAAC;AAAA,kBACpH,GAAG;AAAA,kBACF,IAAI,cAAc,SAAM,IAAI,UAAU;AAAA,mBACzC;AAAA,iBACF;AAAA,eACF;AAAA;AAAA,UA1BK,IAAI;AAAA,QA2BX,CACD,GACH;AAAA,QAEF,oBAAC,SAAI,WAAU,QACb;AAAA,UAAC;AAAA;AAAA,YACC,MAAK;AAAA,YACL,SAAQ;AAAA,YACR,MAAK;AAAA,YACL,SAAS;AAAA,YACT,UAAU;AAAA,YAET,4BACC,iCACE;AAAA,kCAAC,WAAQ,MAAK,MAAK,WAAU,QAAO;AAAA,cACnC,EAAE,gCAAgC,YAAY;AAAA,eACjD,IAEA,EAAE,gCAAgC,SAAS;AAAA;AAAA,QAE/C,GACF;AAAA,SACF;AAAA,OACF;AAAA,IAGC,2BACC,oBAAC,SAAI,WAAU,mEACb,+BAAC,SAAI,WAAU,uEACb;AAAA,0BAAC,QAAG,WAAU,8BAA8B,YAAE,wCAAwC,iBAAiB,GAAE;AAAA,MACzG,oBAAC,OAAE,WAAU,sCACV,YAAE,8CAA8C,8EAA8E,GACjI;AAAA,MACA,qBAAC,SAAI,WAAU,0BACb;AAAA,4BAAC,UAAO,MAAK,UAAS,SAAQ,WAAU,SAAS,2BAC9C,YAAE,kCAAkC,QAAQ,GAC/C;AAAA,QACA,oBAAC,UAAO,MAAK,UAAS,SAAQ,WAAU,SAAS,4BAC9C,YAAE,yCAAyC,eAAe,GAC7D;AAAA,SACF;AAAA,OACF,GACF;AAAA,IAID,8BAA8B,0BAC7B,oBAAC,SAAI,WAAU,mEACb,+BAAC,SAAI,WAAU,uEACb;AAAA,0BAAC,QAAG,WAAU,8BAA8B,YAAE,gCAAgC,yBAAyB,GAAE;AAAA,MACzG,oBAAC,OAAE,WAAU,sCACV,YAAE,sCAAsC,mEAAmE,GAC9G;AAAA,MACA,qBAAC,SAAI,WAAU,2CACb;AAAA,6BAAC,OAAE,WAAU,eACV;AAAA,6BAAmB,kBAChB,GAAG,oBAAoB,kBAAkB,gBAAgB,UAAU,EAAE,IAAI,KAAK,kBAAkB,gBAAgB,KAAK,MACrH;AAAA,UACH;AAAA,UACA,oBAAoB,uBAAuB,UAAU,EAAE;AAAA,UAAK;AAAA,UAAG,uBAAuB;AAAA,UAAM;AAAA,WAC/F;AAAA,QACA,qBAAC,OAAE,WAAU,yBACV;AAAA,6BAAmB,oBAAoB;AAAA,UAAM;AAAA,UAAI,uBAAuB;AAAA,UAAU;AAAA,WACrF;AAAA,SACF;AAAA,MACA,qBAAC,QAAG,WAAU,0BACZ;AAAA,6BAAC,QAAG,WAAU,0BACZ;AAAA,8BAAC,UAAK,WAAU,oBAAmB,oBAAC;AAAA,UACpC,oBAAC,UAAM,YAAE,kCAAkC,sCAAsC,GAAE;AAAA,WACrF;AAAA,QACA,qBAAC,QAAG,WAAU,0BACZ;AAAA,8BAAC,UAAK,WAAU,oBAAmB,oBAAC;AAAA,UACpC,oBAAC,UAAM,YAAE,kCAAkC,kDAAkD,GAAE;AAAA,WACjG;AAAA,SACF;AAAA,MACA,qBAAC,SAAI,WAAU,0BACb;AAAA,4BAAC,UAAO,MAAK,UAAS,SAAQ,WAAU,SAAS,6BAA6B,UAAU,iBACrF,YAAE,kCAAkC,QAAQ,GAC/C;AAAA,QACA,qBAAC,UAAO,MAAK,UAAS,SAAQ,eAAc,SAAS,8BAA8B,UAAU,iBAC1F;AAAA,4BAAkB,oBAAC,WAAQ,MAAK,MAAK,WAAU,QAAO,IAAK;AAAA,UAC3D,EAAE,mCAAmC,SAAS;AAAA,WACjD;AAAA,SACF;AAAA,OACF,GACF;AAAA,KAEJ;AAEJ;AAEA,IAAO,8BAAQ;",
6
6
  "names": []
7
7
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@open-mercato/search",
3
- "version": "0.4.6-develop-e321a4e2a1",
3
+ "version": "0.4.6-main-24e64eef39",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "exports": {
@@ -126,9 +126,9 @@
126
126
  "zod": "^4.0.0"
127
127
  },
128
128
  "peerDependencies": {
129
- "@open-mercato/core": "0.4.6-develop-e321a4e2a1",
130
- "@open-mercato/queue": "0.4.6-develop-e321a4e2a1",
131
- "@open-mercato/shared": "0.4.6-develop-e321a4e2a1"
129
+ "@open-mercato/core": "0.4.6-main-24e64eef39",
130
+ "@open-mercato/queue": "0.4.6-main-24e64eef39",
131
+ "@open-mercato/shared": "0.4.6-main-24e64eef39"
132
132
  },
133
133
  "devDependencies": {
134
134
  "@types/jest": "^30.0.0",
package/src/di.ts CHANGED
@@ -17,7 +17,6 @@ import type {
17
17
  import type { VectorDriver } from './vector/types'
18
18
  import type { QueryEngine } from '@open-mercato/shared/lib/query/types'
19
19
  import type { EntityId } from '@open-mercato/shared/modules/entities'
20
- import type { SearchStrategyId } from '@open-mercato/shared/modules/search'
21
20
  import type { Queue } from '@open-mercato/queue'
22
21
  import type { FulltextIndexJobPayload } from './queue/fulltext-indexing'
23
22
  import type { VectorIndexJobPayload } from './queue/vector-indexing'
@@ -90,7 +89,7 @@ export interface SearchContainer {
90
89
  */
91
90
  export type SearchModuleOptions = {
92
91
  /** Override default strategies to use */
93
- defaultStrategies?: SearchStrategyId[]
92
+ defaultStrategies?: string[]
94
93
  /** Override merge configuration */
95
94
  mergeConfig?: ResultMergeConfig
96
95
  /** Skip token strategy registration */
@@ -266,9 +265,9 @@ export function registerSearchModule(
266
265
  * Determine default strategy order based on available strategies.
267
266
  * Prefers fulltext > vector > tokens.
268
267
  */
269
- function determineDefaultStrategies(strategies: SearchStrategy[]): SearchStrategyId[] {
268
+ function determineDefaultStrategies(strategies: SearchStrategy[]): string[] {
270
269
  const available = new Set(strategies.map((s) => s.id))
271
- const defaults: SearchStrategyId[] = []
270
+ const defaults: string[] = []
272
271
 
273
272
  if (available.has('fulltext')) defaults.push('fulltext')
274
273
  if (available.has('vector')) defaults.push('vector')
@@ -15,7 +15,7 @@ export type FullTextSearchDriverId =
15
15
  | 'algolia'
16
16
  | 'elasticsearch'
17
17
  | 'typesense'
18
- | (string & Record<string, never>)
18
+ | (string & {})
19
19
 
20
20
  // =============================================================================
21
21
  // Document Types (for indexing)
@@ -194,7 +194,7 @@ export async function POST(req: Request) {
194
194
  organizationId: orgId,
195
195
  recreateIndex: true,
196
196
  useQueue,
197
- onProgress: (progress) => {
197
+ onProgress: async (progress) => {
198
198
  searchDebug('search.reindex', 'Progress', progress)
199
199
  // Note: Heartbeat is updated by workers during job processing, not during enqueueing
200
200
  },
@@ -250,7 +250,7 @@ export async function POST(req: Request) {
250
250
  organizationId: orgId,
251
251
  recreateIndex: true,
252
252
  useQueue,
253
- onProgress: (progress) => {
253
+ onProgress: async (progress) => {
254
254
  searchDebug('search.reindex', 'Progress', progress)
255
255
  // Note: Heartbeat is updated by workers during job processing, not during enqueueing
256
256
  },
@@ -23,7 +23,7 @@ function parseLimit(value: string | null): number {
23
23
 
24
24
  function parseStrategies(value: string | null): SearchStrategyId[] | undefined {
25
25
  if (!value) return undefined
26
- const strategies = value.split(',').map((s) => s.trim()).filter(Boolean) as SearchStrategyId[]
26
+ const strategies = value.split(',').map((s) => s.trim()).filter(Boolean)
27
27
  return strategies.length > 0 ? strategies : undefined
28
28
  }
29
29
 
@@ -594,7 +594,7 @@ export function VectorSearchSection({
594
594
 
595
595
  {/* Model Selection */}
596
596
  {isSelected && isConfigured && (
597
- <div className="mt-3 pt-3 border-t border-border space-y-2" onClick={(e) => e.stopPropagation()} onKeyDown={(e) => e.stopPropagation()} role="presentation">
597
+ <div className="mt-3 pt-3 border-t border-border space-y-2" onClick={(e) => e.stopPropagation()}>
598
598
  <div className="space-y-1">
599
599
  <Label htmlFor={`model-${providerId}`} className="text-xs font-medium">
600
600
  {t('search.settings.model.label', 'Model')}