@sonicjs-cms/core 2.4.0 → 2.6.0

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.
Files changed (98) hide show
  1. package/dist/{app-Db0AfT5F.d.cts → app-DV27cjPy.d.cts} +1 -1
  2. package/dist/{app-Db0AfT5F.d.ts → app-DV27cjPy.d.ts} +1 -1
  3. package/dist/{chunk-YIXSSJWD.cjs → chunk-63K7XXRX.cjs} +5 -5
  4. package/dist/{chunk-YIXSSJWD.cjs.map → chunk-63K7XXRX.cjs.map} +1 -1
  5. package/dist/{chunk-VNCYCH3H.js → chunk-7DL5SPPX.js} +59 -5
  6. package/dist/chunk-7DL5SPPX.js.map +1 -0
  7. package/dist/{chunk-AZLU3ROK.cjs → chunk-BZC4FYW7.cjs} +4 -4
  8. package/dist/chunk-BZC4FYW7.cjs.map +1 -0
  9. package/dist/chunk-CLIH2T74.js +403 -0
  10. package/dist/chunk-CLIH2T74.js.map +1 -0
  11. package/dist/{chunk-D2NLCPO2.js → chunk-EVZOVYLO.js} +53 -2
  12. package/dist/chunk-EVZOVYLO.js.map +1 -0
  13. package/dist/{chunk-DXM575E2.js → chunk-EYWR6UA2.js} +6 -6
  14. package/dist/chunk-EYWR6UA2.js.map +1 -0
  15. package/dist/{chunk-CPXAVWCU.js → chunk-F332TENF.js} +278 -3
  16. package/dist/chunk-F332TENF.js.map +1 -0
  17. package/dist/{chunk-FT6NBHNX.js → chunk-F6GZURXJ.js} +2536 -600
  18. package/dist/chunk-F6GZURXJ.js.map +1 -0
  19. package/dist/{chunk-2MI3LZFH.cjs → chunk-IIRVZSP2.cjs} +53 -2
  20. package/dist/chunk-IIRVZSP2.cjs.map +1 -0
  21. package/dist/{chunk-V5LBQN3I.js → chunk-KA2PDJNB.js} +4 -4
  22. package/dist/chunk-KA2PDJNB.js.map +1 -0
  23. package/dist/{chunk-AVPUX57O.js → chunk-KAOWRIFD.js} +3 -3
  24. package/dist/{chunk-AVPUX57O.js.map → chunk-KAOWRIFD.js.map} +1 -1
  25. package/dist/{chunk-ILZ3DP4I.cjs → chunk-MPT5PA6U.cjs} +24 -2
  26. package/dist/chunk-MPT5PA6U.cjs.map +1 -0
  27. package/dist/{chunk-A4SVOGG6.cjs → chunk-N7TDLOUE.cjs} +2696 -762
  28. package/dist/chunk-N7TDLOUE.cjs.map +1 -0
  29. package/dist/{chunk-7I5INVNR.cjs → chunk-T3YIKW2A.cjs} +9 -9
  30. package/dist/chunk-T3YIKW2A.cjs.map +1 -0
  31. package/dist/{chunk-DTLB6UIH.cjs → chunk-Y72M3MVX.cjs} +280 -2
  32. package/dist/chunk-Y72M3MVX.cjs.map +1 -0
  33. package/dist/{chunk-SGAG6FD3.js → chunk-YFJJU26H.js} +24 -2
  34. package/dist/chunk-YFJJU26H.js.map +1 -0
  35. package/dist/chunk-YHW27CBV.cjs +406 -0
  36. package/dist/chunk-YHW27CBV.cjs.map +1 -0
  37. package/dist/{chunk-FYEDK7K7.cjs → chunk-YMTTGHEK.cjs} +61 -4
  38. package/dist/chunk-YMTTGHEK.cjs.map +1 -0
  39. package/dist/{collection-config-FLlGtsh9.d.cts → collection-config-BF95LgQb.d.cts} +10 -2
  40. package/dist/{collection-config-FLlGtsh9.d.ts → collection-config-BF95LgQb.d.ts} +10 -2
  41. package/dist/index.cjs +2001 -142
  42. package/dist/index.cjs.map +1 -1
  43. package/dist/index.d.cts +504 -9
  44. package/dist/index.d.ts +504 -9
  45. package/dist/index.js +1893 -41
  46. package/dist/index.js.map +1 -1
  47. package/dist/middleware.cjs +24 -24
  48. package/dist/middleware.d.cts +1 -1
  49. package/dist/middleware.d.ts +1 -1
  50. package/dist/middleware.js +3 -3
  51. package/dist/migrations-QNYAWQLB.cjs +13 -0
  52. package/dist/{migrations-32QAYLWJ.cjs.map → migrations-QNYAWQLB.cjs.map} +1 -1
  53. package/dist/migrations-R6NQBKQV.js +4 -0
  54. package/dist/{migrations-57ZHBQ4X.js.map → migrations-R6NQBKQV.js.map} +1 -1
  55. package/dist/{plugin-bootstrap-CDh0JHtW.d.ts → plugin-bootstrap-CB-xaBfK.d.ts} +2 -2
  56. package/dist/{plugin-bootstrap-C0E3jdz-.d.cts → plugin-bootstrap-U-cw9jn3.d.cts} +2 -2
  57. package/dist/plugin-manager-Baa6xXqB.d.ts +328 -0
  58. package/dist/plugin-manager-vBal9Zip.d.cts +328 -0
  59. package/dist/plugins.cjs +20 -7
  60. package/dist/plugins.d.cts +53 -310
  61. package/dist/plugins.d.ts +53 -310
  62. package/dist/plugins.js +2 -1
  63. package/dist/routes.cjs +27 -26
  64. package/dist/routes.d.cts +1 -1
  65. package/dist/routes.d.ts +1 -1
  66. package/dist/routes.js +7 -6
  67. package/dist/services.cjs +16 -16
  68. package/dist/services.d.cts +2 -2
  69. package/dist/services.d.ts +2 -2
  70. package/dist/services.js +2 -2
  71. package/dist/templates.cjs +17 -17
  72. package/dist/templates.js +2 -2
  73. package/dist/types.d.cts +1 -1
  74. package/dist/types.d.ts +1 -1
  75. package/dist/utils.cjs +23 -11
  76. package/dist/utils.d.cts +38 -1
  77. package/dist/utils.d.ts +38 -1
  78. package/dist/utils.js +1 -1
  79. package/migrations/027_fix_slug_field_type.sql +18 -0
  80. package/migrations/028_fix_slug_field_type_in_schemas.sql +30 -0
  81. package/migrations/029_ai_search_plugin.sql +45 -0
  82. package/package.json +5 -2
  83. package/dist/chunk-2MI3LZFH.cjs.map +0 -1
  84. package/dist/chunk-7I5INVNR.cjs.map +0 -1
  85. package/dist/chunk-A4SVOGG6.cjs.map +0 -1
  86. package/dist/chunk-AZLU3ROK.cjs.map +0 -1
  87. package/dist/chunk-CPXAVWCU.js.map +0 -1
  88. package/dist/chunk-D2NLCPO2.js.map +0 -1
  89. package/dist/chunk-DTLB6UIH.cjs.map +0 -1
  90. package/dist/chunk-DXM575E2.js.map +0 -1
  91. package/dist/chunk-FT6NBHNX.js.map +0 -1
  92. package/dist/chunk-FYEDK7K7.cjs.map +0 -1
  93. package/dist/chunk-ILZ3DP4I.cjs.map +0 -1
  94. package/dist/chunk-SGAG6FD3.js.map +0 -1
  95. package/dist/chunk-V5LBQN3I.js.map +0 -1
  96. package/dist/chunk-VNCYCH3H.js.map +0 -1
  97. package/dist/migrations-32QAYLWJ.cjs +0 -13
  98. package/dist/migrations-57ZHBQ4X.js +0 -4
package/dist/index.cjs CHANGED
@@ -1,14 +1,15 @@
1
1
  'use strict';
2
2
 
3
- var chunkA4SVOGG6_cjs = require('./chunk-A4SVOGG6.cjs');
3
+ var chunkN7TDLOUE_cjs = require('./chunk-N7TDLOUE.cjs');
4
4
  var chunk7FOAMNTI_cjs = require('./chunk-7FOAMNTI.cjs');
5
- var chunk7I5INVNR_cjs = require('./chunk-7I5INVNR.cjs');
6
- var chunkILZ3DP4I_cjs = require('./chunk-ILZ3DP4I.cjs');
7
- var chunk2MI3LZFH_cjs = require('./chunk-2MI3LZFH.cjs');
8
- var chunkYIXSSJWD_cjs = require('./chunk-YIXSSJWD.cjs');
9
- var chunkAZLU3ROK_cjs = require('./chunk-AZLU3ROK.cjs');
10
- var chunkDTLB6UIH_cjs = require('./chunk-DTLB6UIH.cjs');
11
- var chunkFYEDK7K7_cjs = require('./chunk-FYEDK7K7.cjs');
5
+ var chunkT3YIKW2A_cjs = require('./chunk-T3YIKW2A.cjs');
6
+ var chunkMPT5PA6U_cjs = require('./chunk-MPT5PA6U.cjs');
7
+ var chunkIIRVZSP2_cjs = require('./chunk-IIRVZSP2.cjs');
8
+ var chunk63K7XXRX_cjs = require('./chunk-63K7XXRX.cjs');
9
+ var chunkBZC4FYW7_cjs = require('./chunk-BZC4FYW7.cjs');
10
+ var chunkY72M3MVX_cjs = require('./chunk-Y72M3MVX.cjs');
11
+ var chunkYHW27CBV_cjs = require('./chunk-YHW27CBV.cjs');
12
+ var chunkYMTTGHEK_cjs = require('./chunk-YMTTGHEK.cjs');
12
13
  require('./chunk-P3XDZL6Q.cjs');
13
14
  var chunkRCQ2HIQD_cjs = require('./chunk-RCQ2HIQD.cjs');
14
15
  var chunkKYGRJCZM_cjs = require('./chunk-KYGRJCZM.cjs');
@@ -19,31 +20,6 @@ var cookie = require('hono/cookie');
19
20
  var zod = require('zod');
20
21
  var d1 = require('drizzle-orm/d1');
21
22
 
22
- // src/middleware/admin-setup.ts
23
- function adminSetupMiddleware() {
24
- return async (c, next) => {
25
- const path = new URL(c.req.url).pathname;
26
- if (path.startsWith("/auth/")) {
27
- return next();
28
- }
29
- if (path.match(/\.(js|css|ico|png|jpg|jpeg|gif|svg|woff|woff2|ttf|eot)$/)) {
30
- return next();
31
- }
32
- if (path === "/health") {
33
- return next();
34
- }
35
- if (path.startsWith("/api/")) {
36
- return next();
37
- }
38
- const db = c.env.DB;
39
- const adminExists = await chunkA4SVOGG6_cjs.checkAdminUserExists(db);
40
- if (!adminExists) {
41
- return c.redirect("/auth/register?setup=true");
42
- }
43
- return next();
44
- };
45
- }
46
-
47
23
  // src/plugins/core-plugins/database-tools-plugin/services/database-service.ts
48
24
  var DatabaseToolsService = class {
49
25
  constructor(db) {
@@ -259,7 +235,7 @@ var DatabaseToolsService = class {
259
235
  };
260
236
 
261
237
  // src/templates/pages/admin-database-table.template.ts
262
- chunkAZLU3ROK_cjs.init_admin_layout_catalyst_template();
238
+ chunkBZC4FYW7_cjs.init_admin_layout_catalyst_template();
263
239
  function renderDatabaseTablePage(data) {
264
240
  const totalPages = Math.ceil(data.totalRows / data.pageSize);
265
241
  const startRow = (data.currentPage - 1) * data.pageSize + 1;
@@ -508,7 +484,7 @@ function renderDatabaseTablePage(data) {
508
484
  user: data.user,
509
485
  content: pageContent
510
486
  };
511
- return chunkAZLU3ROK_cjs.renderAdminLayoutCatalyst(layoutData);
487
+ return chunkBZC4FYW7_cjs.renderAdminLayoutCatalyst(layoutData);
512
488
  }
513
489
  function generatePageNumbers(currentPage, totalPages) {
514
490
  const pages = [];
@@ -583,7 +559,7 @@ function formatCellValue(value) {
583
559
  // src/plugins/core-plugins/database-tools-plugin/admin-routes.ts
584
560
  function createDatabaseToolsAdminRoutes() {
585
561
  const router2 = new hono.Hono();
586
- router2.use("*", chunk7I5INVNR_cjs.requireAuth());
562
+ router2.use("*", chunkT3YIKW2A_cjs.requireAuth());
587
563
  router2.get("/api/stats", async (c) => {
588
564
  try {
589
565
  const user = c.get("user");
@@ -1331,7 +1307,7 @@ function createSeedDataAdminRoutes() {
1331
1307
  return routes;
1332
1308
  }
1333
1309
  function createEmailPlugin() {
1334
- const builder = chunkA4SVOGG6_cjs.PluginBuilder.create({
1310
+ const builder = chunkYHW27CBV_cjs.PluginBuilder.create({
1335
1311
  name: "email",
1336
1312
  version: "1.0.0-beta.1",
1337
1313
  description: "Send transactional emails using Resend"
@@ -1584,7 +1560,7 @@ function createEmailPlugin() {
1584
1560
  role: user.role ?? "admin"
1585
1561
  } : void 0;
1586
1562
  return c.html(
1587
- chunkAZLU3ROK_cjs.renderAdminLayout({
1563
+ chunkBZC4FYW7_cjs.renderAdminLayout({
1588
1564
  title: "Email Settings",
1589
1565
  content: contentHTML,
1590
1566
  user: templateUser,
@@ -2008,7 +1984,7 @@ var DEFAULT_SETTINGS = {
2008
1984
  appName: "SonicJS"
2009
1985
  };
2010
1986
  function createOTPLoginPlugin() {
2011
- const builder = chunkA4SVOGG6_cjs.PluginBuilder.create({
1987
+ const builder = chunkYHW27CBV_cjs.PluginBuilder.create({
2012
1988
  name: "otp-login",
2013
1989
  version: "1.0.0-beta.1",
2014
1990
  description: "Passwordless authentication via email one-time codes"
@@ -2172,7 +2148,7 @@ function createOTPLoginPlugin() {
2172
2148
  error: "Account is deactivated"
2173
2149
  }, 403);
2174
2150
  }
2175
- const token = await chunk7I5INVNR_cjs.AuthManager.generateToken(user.id, user.email, user.role);
2151
+ const token = await chunkT3YIKW2A_cjs.AuthManager.generateToken(user.id, user.email, user.role);
2176
2152
  cookie.setCookie(c, "auth_token", token, {
2177
2153
  httpOnly: true,
2178
2154
  secure: true,
@@ -2227,8 +2203,8 @@ function createOTPLoginPlugin() {
2227
2203
  requiresAuth: false,
2228
2204
  priority: 100
2229
2205
  });
2230
- const adminRoutes = new hono.Hono();
2231
- adminRoutes.get("/settings", async (c) => {
2206
+ const adminRoutes2 = new hono.Hono();
2207
+ adminRoutes2.get("/settings", async (c) => {
2232
2208
  const user = c.get("user");
2233
2209
  const contentHTML = await html.html`
2234
2210
  <div class="p-8">
@@ -2400,7 +2376,7 @@ function createOTPLoginPlugin() {
2400
2376
  role: user.role ?? "admin"
2401
2377
  } : void 0;
2402
2378
  return c.html(
2403
- chunkAZLU3ROK_cjs.adminLayoutV2({
2379
+ chunkBZC4FYW7_cjs.adminLayoutV2({
2404
2380
  title: "OTP Login Settings",
2405
2381
  content: contentHTML,
2406
2382
  user: templateUser,
@@ -2408,7 +2384,7 @@ function createOTPLoginPlugin() {
2408
2384
  })
2409
2385
  );
2410
2386
  });
2411
- builder.addRoute("/admin/plugins/otp-login", adminRoutes, {
2387
+ builder.addRoute("/admin/plugins/otp-login", adminRoutes2, {
2412
2388
  description: "OTP login admin interface",
2413
2389
  requiresAuth: true,
2414
2390
  priority: 85
@@ -2429,6 +2405,1840 @@ function createOTPLoginPlugin() {
2429
2405
  return builder.build();
2430
2406
  }
2431
2407
  var otpLoginPlugin = createOTPLoginPlugin();
2408
+
2409
+ // src/plugins/core-plugins/ai-search-plugin/services/embedding.service.ts
2410
+ var EmbeddingService = class {
2411
+ constructor(ai) {
2412
+ this.ai = ai;
2413
+ }
2414
+ /**
2415
+ * Generate embedding for a single text
2416
+ */
2417
+ async generateEmbedding(text) {
2418
+ try {
2419
+ const response = await this.ai.run("@cf/baai/bge-base-en-v1.5", {
2420
+ text: this.preprocessText(text)
2421
+ });
2422
+ if (response.data && response.data.length > 0) {
2423
+ return response.data[0];
2424
+ }
2425
+ throw new Error("No embedding data returned");
2426
+ } catch (error) {
2427
+ console.error("[EmbeddingService] Error generating embedding:", error);
2428
+ throw error;
2429
+ }
2430
+ }
2431
+ /**
2432
+ * Generate embeddings for multiple texts (batch processing)
2433
+ */
2434
+ async generateBatch(texts) {
2435
+ try {
2436
+ const batchSize = 10;
2437
+ const batches = [];
2438
+ for (let i = 0; i < texts.length; i += batchSize) {
2439
+ batches.push(texts.slice(i, i + batchSize));
2440
+ }
2441
+ const allEmbeddings = [];
2442
+ for (const batch of batches) {
2443
+ const batchEmbeddings = await Promise.all(
2444
+ batch.map((text) => this.generateEmbedding(text))
2445
+ );
2446
+ allEmbeddings.push(...batchEmbeddings);
2447
+ }
2448
+ return allEmbeddings;
2449
+ } catch (error) {
2450
+ console.error("[EmbeddingService] Error generating batch embeddings:", error);
2451
+ throw error;
2452
+ }
2453
+ }
2454
+ /**
2455
+ * Preprocess text before generating embedding
2456
+ * - Trim whitespace
2457
+ * - Limit length to avoid token limits
2458
+ * - Remove special characters that might cause issues
2459
+ */
2460
+ preprocessText(text) {
2461
+ if (!text) return "";
2462
+ let processed = text.trim().replace(/\s+/g, " ");
2463
+ if (processed.length > 8e3) {
2464
+ processed = processed.substring(0, 8e3);
2465
+ }
2466
+ return processed;
2467
+ }
2468
+ /**
2469
+ * Calculate cosine similarity between two embeddings
2470
+ */
2471
+ cosineSimilarity(a, b) {
2472
+ if (a.length !== b.length) {
2473
+ throw new Error("Embeddings must have same dimensions");
2474
+ }
2475
+ let dotProduct = 0;
2476
+ let normA = 0;
2477
+ let normB = 0;
2478
+ for (let i = 0; i < a.length; i++) {
2479
+ const aVal = a[i] ?? 0;
2480
+ const bVal = b[i] ?? 0;
2481
+ dotProduct += aVal * bVal;
2482
+ normA += aVal * aVal;
2483
+ normB += bVal * bVal;
2484
+ }
2485
+ return dotProduct / (Math.sqrt(normA) * Math.sqrt(normB));
2486
+ }
2487
+ };
2488
+
2489
+ // src/plugins/core-plugins/ai-search-plugin/services/chunking.service.ts
2490
+ var ChunkingService = class {
2491
+ // Default chunk size (in approximate tokens)
2492
+ CHUNK_SIZE = 500;
2493
+ CHUNK_OVERLAP = 50;
2494
+ /**
2495
+ * Chunk a single content item
2496
+ */
2497
+ chunkContent(contentId, collectionId, title, data, metadata = {}) {
2498
+ const text = this.extractText(data);
2499
+ if (!text || text.trim().length === 0) {
2500
+ console.warn(`[ChunkingService] No text found for content ${contentId}`);
2501
+ return [];
2502
+ }
2503
+ const textChunks = this.splitIntoChunks(text);
2504
+ return textChunks.map((chunkText, index) => ({
2505
+ id: `${contentId}_chunk_${index}`,
2506
+ content_id: contentId,
2507
+ collection_id: collectionId,
2508
+ title,
2509
+ text: chunkText,
2510
+ chunk_index: index,
2511
+ metadata: {
2512
+ ...metadata,
2513
+ total_chunks: textChunks.length
2514
+ }
2515
+ }));
2516
+ }
2517
+ /**
2518
+ * Chunk multiple content items
2519
+ */
2520
+ chunkContentBatch(items) {
2521
+ const allChunks = [];
2522
+ for (const item of items) {
2523
+ const chunks = this.chunkContent(
2524
+ item.id,
2525
+ item.collection_id,
2526
+ item.title,
2527
+ item.data,
2528
+ item.metadata
2529
+ );
2530
+ allChunks.push(...chunks);
2531
+ }
2532
+ return allChunks;
2533
+ }
2534
+ /**
2535
+ * Extract all text from content data
2536
+ */
2537
+ extractText(data) {
2538
+ const parts = [];
2539
+ if (data.title) parts.push(String(data.title));
2540
+ if (data.name) parts.push(String(data.name));
2541
+ if (data.description) parts.push(String(data.description));
2542
+ if (data.content) parts.push(String(data.content));
2543
+ if (data.body) parts.push(String(data.body));
2544
+ if (data.text) parts.push(String(data.text));
2545
+ if (data.summary) parts.push(String(data.summary));
2546
+ const extractRecursive = (obj) => {
2547
+ if (typeof obj === "string") {
2548
+ if (obj.length > 10 && !obj.startsWith("http")) {
2549
+ parts.push(obj);
2550
+ }
2551
+ } else if (Array.isArray(obj)) {
2552
+ obj.forEach(extractRecursive);
2553
+ } else if (obj && typeof obj === "object") {
2554
+ const skipKeys = ["id", "slug", "url", "image", "thumbnail", "metadata"];
2555
+ Object.entries(obj).forEach(([key, value]) => {
2556
+ if (!skipKeys.includes(key.toLowerCase())) {
2557
+ extractRecursive(value);
2558
+ }
2559
+ });
2560
+ }
2561
+ };
2562
+ extractRecursive(data);
2563
+ return parts.join("\n\n").trim();
2564
+ }
2565
+ /**
2566
+ * Split text into overlapping chunks
2567
+ */
2568
+ splitIntoChunks(text) {
2569
+ const words = text.split(/\s+/);
2570
+ if (words.length <= this.CHUNK_SIZE) {
2571
+ return [text];
2572
+ }
2573
+ const chunks = [];
2574
+ let startIndex = 0;
2575
+ while (startIndex < words.length) {
2576
+ const endIndex = Math.min(startIndex + this.CHUNK_SIZE, words.length);
2577
+ const chunk = words.slice(startIndex, endIndex).join(" ");
2578
+ chunks.push(chunk);
2579
+ startIndex += this.CHUNK_SIZE - this.CHUNK_OVERLAP;
2580
+ if (startIndex >= words.length - this.CHUNK_OVERLAP) {
2581
+ break;
2582
+ }
2583
+ }
2584
+ return chunks;
2585
+ }
2586
+ /**
2587
+ * Get optimal chunk size based on content type
2588
+ */
2589
+ getOptimalChunkSize(contentType) {
2590
+ switch (contentType) {
2591
+ case "blog_posts":
2592
+ case "articles":
2593
+ return 600;
2594
+ // Larger chunks for long-form content
2595
+ case "products":
2596
+ case "pages":
2597
+ return 400;
2598
+ // Medium chunks for structured content
2599
+ case "messages":
2600
+ case "comments":
2601
+ return 200;
2602
+ // Small chunks for short content
2603
+ default:
2604
+ return this.CHUNK_SIZE;
2605
+ }
2606
+ }
2607
+ };
2608
+
2609
+ // src/plugins/core-plugins/ai-search-plugin/services/custom-rag.service.ts
2610
+ var CustomRAGService = class {
2611
+ constructor(db, ai, vectorize) {
2612
+ this.db = db;
2613
+ this.ai = ai;
2614
+ this.vectorize = vectorize;
2615
+ this.embeddingService = new EmbeddingService(ai);
2616
+ this.chunkingService = new ChunkingService();
2617
+ }
2618
+ embeddingService;
2619
+ chunkingService;
2620
+ /**
2621
+ * Index all content from a collection
2622
+ */
2623
+ async indexCollection(collectionId) {
2624
+ console.log(`[CustomRAG] Starting indexing for collection: ${collectionId}`);
2625
+ try {
2626
+ const { results: contentItems } = await this.db.prepare(`
2627
+ SELECT c.id, c.title, c.data, c.collection_id, c.status,
2628
+ c.created_at, c.updated_at, c.author_id,
2629
+ col.name as collection_name, col.display_name as collection_display_name
2630
+ FROM content c
2631
+ JOIN collections col ON c.collection_id = col.id
2632
+ WHERE c.collection_id = ? AND c.status = 'published'
2633
+ `).bind(collectionId).all();
2634
+ const totalItems = contentItems?.length || 0;
2635
+ if (totalItems === 0) {
2636
+ console.log(`[CustomRAG] No content found in collection ${collectionId}`);
2637
+ return { total_items: 0, total_chunks: 0, indexed_chunks: 0, errors: 0 };
2638
+ }
2639
+ const items = (contentItems || []).map((item) => ({
2640
+ id: item.id,
2641
+ collection_id: item.collection_id,
2642
+ title: item.title || "Untitled",
2643
+ data: typeof item.data === "string" ? JSON.parse(item.data) : item.data,
2644
+ metadata: {
2645
+ status: item.status,
2646
+ created_at: item.created_at,
2647
+ updated_at: item.updated_at,
2648
+ author_id: item.author_id,
2649
+ collection_name: item.collection_name,
2650
+ collection_display_name: item.collection_display_name
2651
+ }
2652
+ }));
2653
+ const chunks = this.chunkingService.chunkContentBatch(items);
2654
+ const totalChunks = chunks.length;
2655
+ console.log(`[CustomRAG] Generated ${totalChunks} chunks from ${totalItems} items`);
2656
+ const embeddings = await this.embeddingService.generateBatch(
2657
+ chunks.map((c) => `${c.title}
2658
+
2659
+ ${c.text}`)
2660
+ );
2661
+ console.log(`[CustomRAG] Generated ${embeddings.length} embeddings`);
2662
+ let indexedChunks = 0;
2663
+ let errors = 0;
2664
+ const batchSize = 100;
2665
+ for (let i = 0; i < chunks.length; i += batchSize) {
2666
+ const chunkBatch = chunks.slice(i, i + batchSize);
2667
+ const embeddingBatch = embeddings.slice(i, i + batchSize);
2668
+ try {
2669
+ await this.vectorize.upsert(
2670
+ chunkBatch.map((chunk, idx) => ({
2671
+ id: chunk.id,
2672
+ values: embeddingBatch[idx],
2673
+ metadata: {
2674
+ content_id: chunk.content_id,
2675
+ collection_id: chunk.collection_id,
2676
+ title: chunk.title,
2677
+ text: chunk.text.substring(0, 500),
2678
+ // Store snippet for display
2679
+ chunk_index: chunk.chunk_index,
2680
+ ...chunk.metadata
2681
+ }
2682
+ }))
2683
+ );
2684
+ indexedChunks += chunkBatch.length;
2685
+ console.log(`[CustomRAG] Indexed batch ${i / batchSize + 1}: ${chunkBatch.length} chunks`);
2686
+ } catch (error) {
2687
+ console.error(`[CustomRAG] Error indexing batch ${i / batchSize + 1}:`, error);
2688
+ errors += chunkBatch.length;
2689
+ }
2690
+ }
2691
+ console.log(`[CustomRAG] Indexing complete: ${indexedChunks}/${totalChunks} chunks indexed`);
2692
+ return {
2693
+ total_items: totalItems,
2694
+ total_chunks: totalChunks,
2695
+ indexed_chunks: indexedChunks,
2696
+ errors
2697
+ };
2698
+ } catch (error) {
2699
+ console.error(`[CustomRAG] Error indexing collection ${collectionId}:`, error);
2700
+ throw error;
2701
+ }
2702
+ }
2703
+ /**
2704
+ * Search using RAG (semantic search with Vectorize)
2705
+ */
2706
+ async search(query, settings) {
2707
+ const startTime = Date.now();
2708
+ try {
2709
+ console.log(`[CustomRAG] Searching for: "${query.query}"`);
2710
+ const queryEmbedding = await this.embeddingService.generateEmbedding(query.query);
2711
+ const filter = {};
2712
+ if (query.filters?.collections && query.filters.collections.length > 0) {
2713
+ filter.collection_id = { $in: query.filters.collections };
2714
+ } else if (settings.selected_collections.length > 0) {
2715
+ filter.collection_id = { $in: settings.selected_collections };
2716
+ }
2717
+ if (query.filters?.status && query.filters.status.length > 0) {
2718
+ filter.status = { $in: query.filters.status };
2719
+ }
2720
+ const vectorResults = await this.vectorize.query(queryEmbedding, {
2721
+ topK: 50,
2722
+ // Max allowed with returnMetadata: true
2723
+ returnMetadata: true
2724
+ });
2725
+ let filteredMatches = vectorResults.matches || [];
2726
+ if (filter.collection_id?.$in && Array.isArray(filter.collection_id.$in)) {
2727
+ const allowedCollections = filter.collection_id.$in;
2728
+ filteredMatches = filteredMatches.filter(
2729
+ (match) => allowedCollections.includes(match.metadata?.collection_id)
2730
+ );
2731
+ }
2732
+ if (filter.status?.$in && Array.isArray(filter.status.$in)) {
2733
+ const allowedStatuses = filter.status.$in;
2734
+ filteredMatches = filteredMatches.filter(
2735
+ (match) => allowedStatuses.includes(match.metadata?.status)
2736
+ );
2737
+ }
2738
+ const topK = query.limit || settings.results_limit || 20;
2739
+ filteredMatches = filteredMatches.slice(0, topK);
2740
+ vectorResults.matches = filteredMatches;
2741
+ if (!vectorResults.matches || vectorResults.matches.length === 0) {
2742
+ return {
2743
+ results: [],
2744
+ total: 0,
2745
+ query_time_ms: Date.now() - startTime,
2746
+ mode: "ai"
2747
+ };
2748
+ }
2749
+ const contentIds = [...new Set(
2750
+ vectorResults.matches.map((m) => m.metadata.content_id)
2751
+ )];
2752
+ const placeholders = contentIds.map(() => "?").join(",");
2753
+ const { results: contentItems } = await this.db.prepare(`
2754
+ SELECT c.id, c.title, c.slug, c.collection_id, c.status,
2755
+ c.created_at, c.updated_at, c.author_id,
2756
+ col.display_name as collection_name
2757
+ FROM content c
2758
+ JOIN collections col ON c.collection_id = col.id
2759
+ WHERE c.id IN (${placeholders})
2760
+ `).bind(...contentIds).all();
2761
+ const searchResults = (contentItems || []).map((item) => {
2762
+ const matchingChunks = vectorResults.matches.filter(
2763
+ (m) => m.metadata.content_id === item.id
2764
+ );
2765
+ const bestMatch = matchingChunks.reduce(
2766
+ (best, current) => current.score > (best?.score || 0) ? current : best,
2767
+ null
2768
+ );
2769
+ return {
2770
+ id: item.id,
2771
+ title: item.title || "Untitled",
2772
+ slug: item.slug || "",
2773
+ collection_id: item.collection_id,
2774
+ collection_name: item.collection_name,
2775
+ snippet: bestMatch?.metadata?.text || "",
2776
+ relevance_score: bestMatch?.score || 0,
2777
+ status: item.status,
2778
+ created_at: item.created_at,
2779
+ updated_at: item.updated_at
2780
+ };
2781
+ });
2782
+ searchResults.sort((a, b) => (b.relevance_score || 0) - (a.relevance_score || 0));
2783
+ const queryTime = Date.now() - startTime;
2784
+ console.log(`[CustomRAG] Search completed in ${queryTime}ms, ${searchResults.length} results`);
2785
+ return {
2786
+ results: searchResults,
2787
+ total: searchResults.length,
2788
+ query_time_ms: queryTime,
2789
+ mode: "ai"
2790
+ };
2791
+ } catch (error) {
2792
+ console.error("[CustomRAG] Search error:", error);
2793
+ throw error;
2794
+ }
2795
+ }
2796
+ /**
2797
+ * Update index for a single content item
2798
+ */
2799
+ async updateContentIndex(contentId) {
2800
+ try {
2801
+ const content2 = await this.db.prepare(`
2802
+ SELECT c.id, c.title, c.data, c.collection_id, c.status,
2803
+ c.created_at, c.updated_at, c.author_id,
2804
+ col.name as collection_name, col.display_name as collection_display_name
2805
+ FROM content c
2806
+ JOIN collections col ON c.collection_id = col.id
2807
+ WHERE c.id = ?
2808
+ `).bind(contentId).first();
2809
+ if (!content2) {
2810
+ console.warn(`[CustomRAG] Content ${contentId} not found`);
2811
+ return;
2812
+ }
2813
+ if (content2.status !== "published") {
2814
+ await this.removeContentFromIndex(contentId);
2815
+ return;
2816
+ }
2817
+ const chunks = this.chunkingService.chunkContent(
2818
+ content2.id,
2819
+ content2.collection_id,
2820
+ content2.title || "Untitled",
2821
+ typeof content2.data === "string" ? JSON.parse(content2.data) : content2.data,
2822
+ {
2823
+ status: content2.status,
2824
+ created_at: content2.created_at,
2825
+ updated_at: content2.updated_at,
2826
+ author_id: content2.author_id,
2827
+ collection_name: content2.collection_name,
2828
+ collection_display_name: content2.collection_display_name
2829
+ }
2830
+ );
2831
+ const embeddings = await this.embeddingService.generateBatch(
2832
+ chunks.map((c) => `${c.title}
2833
+
2834
+ ${c.text}`)
2835
+ );
2836
+ await this.vectorize.upsert(
2837
+ chunks.map((chunk, idx) => ({
2838
+ id: chunk.id,
2839
+ values: embeddings[idx],
2840
+ metadata: {
2841
+ content_id: chunk.content_id,
2842
+ collection_id: chunk.collection_id,
2843
+ title: chunk.title,
2844
+ text: chunk.text.substring(0, 500),
2845
+ chunk_index: chunk.chunk_index,
2846
+ ...chunk.metadata
2847
+ }
2848
+ }))
2849
+ );
2850
+ console.log(`[CustomRAG] Updated index for content ${contentId}: ${chunks.length} chunks`);
2851
+ } catch (error) {
2852
+ console.error(`[CustomRAG] Error updating index for ${contentId}:`, error);
2853
+ throw error;
2854
+ }
2855
+ }
2856
+ /**
2857
+ * Remove content from index
2858
+ */
2859
+ async removeContentFromIndex(contentId) {
2860
+ try {
2861
+ console.log(`[CustomRAG] Removing content ${contentId} from index`);
2862
+ } catch (error) {
2863
+ console.error(`[CustomRAG] Error removing content ${contentId}:`, error);
2864
+ throw error;
2865
+ }
2866
+ }
2867
+ /**
2868
+ * Get search suggestions based on query
2869
+ */
2870
+ async getSuggestions(partialQuery, limit = 5) {
2871
+ try {
2872
+ const queryEmbedding = await this.embeddingService.generateEmbedding(partialQuery);
2873
+ const results = await this.vectorize.query(queryEmbedding, {
2874
+ topK: limit * 2,
2875
+ // Get more to filter
2876
+ returnMetadata: true
2877
+ });
2878
+ const suggestions = [...new Set(
2879
+ results.matches?.map((m) => m.metadata.title).filter(Boolean) || []
2880
+ )].slice(0, limit);
2881
+ return suggestions;
2882
+ } catch (error) {
2883
+ console.error("[CustomRAG] Error getting suggestions:", error);
2884
+ return [];
2885
+ }
2886
+ }
2887
+ /**
2888
+ * Check if Vectorize is available and configured
2889
+ */
2890
+ isAvailable() {
2891
+ return !!this.vectorize && !!this.ai;
2892
+ }
2893
+ };
2894
+
2895
+ // src/plugins/core-plugins/ai-search-plugin/services/ai-search.ts
2896
+ var AISearchService = class {
2897
+ constructor(db, ai, vectorize) {
2898
+ this.db = db;
2899
+ this.ai = ai;
2900
+ this.vectorize = vectorize;
2901
+ if (this.ai && this.vectorize) {
2902
+ this.customRAG = new CustomRAGService(db, ai, vectorize);
2903
+ console.log("[AISearchService] Custom RAG initialized");
2904
+ } else {
2905
+ console.log("[AISearchService] Custom RAG not available, using keyword search only");
2906
+ }
2907
+ }
2908
+ customRAG;
2909
+ /**
2910
+ * Get plugin settings
2911
+ */
2912
+ async getSettings() {
2913
+ try {
2914
+ const plugin = await this.db.prepare(`SELECT settings FROM plugins WHERE id = ? LIMIT 1`).bind("ai-search").first();
2915
+ if (!plugin || !plugin.settings) {
2916
+ return this.getDefaultSettings();
2917
+ }
2918
+ return JSON.parse(plugin.settings);
2919
+ } catch (error) {
2920
+ console.error("Error fetching AI Search settings:", error);
2921
+ return this.getDefaultSettings();
2922
+ }
2923
+ }
2924
+ /**
2925
+ * Get default settings
2926
+ */
2927
+ getDefaultSettings() {
2928
+ return {
2929
+ enabled: true,
2930
+ ai_mode_enabled: true,
2931
+ selected_collections: [],
2932
+ dismissed_collections: [],
2933
+ autocomplete_enabled: true,
2934
+ cache_duration: 1,
2935
+ results_limit: 20,
2936
+ index_media: false
2937
+ };
2938
+ }
2939
+ /**
2940
+ * Update plugin settings
2941
+ */
2942
+ async updateSettings(settings) {
2943
+ const existing = await this.getSettings();
2944
+ const updated = {
2945
+ ...existing,
2946
+ ...settings
2947
+ };
2948
+ try {
2949
+ await this.db.prepare(`
2950
+ UPDATE plugins
2951
+ SET settings = ?,
2952
+ updated_at = unixepoch()
2953
+ WHERE id = 'ai-search'
2954
+ `).bind(JSON.stringify(updated)).run();
2955
+ return updated;
2956
+ } catch (error) {
2957
+ console.error("Error updating AI Search settings:", error);
2958
+ throw error;
2959
+ }
2960
+ }
2961
+ /**
2962
+ * Detect new collections that aren't indexed or dismissed
2963
+ */
2964
+ async detectNewCollections() {
2965
+ try {
2966
+ const collectionsStmt = this.db.prepare(
2967
+ "SELECT id, name, display_name, description FROM collections WHERE is_active = 1"
2968
+ );
2969
+ const { results: allCollections } = await collectionsStmt.all();
2970
+ const collections2 = (allCollections || []).filter(
2971
+ (col) => {
2972
+ if (!col.name) return false;
2973
+ const name = col.name.toLowerCase();
2974
+ return !name.startsWith("test_") && !name.endsWith("_test") && name !== "test_collection" && !name.includes("_test_") && name !== "large_payload_test" && name !== "concurrent_test";
2975
+ }
2976
+ );
2977
+ const settings = await this.getSettings();
2978
+ const selected = settings?.selected_collections || [];
2979
+ const dismissed = settings?.dismissed_collections || [];
2980
+ const notifications = [];
2981
+ for (const collection of collections2 || []) {
2982
+ const collectionId = String(collection.id);
2983
+ if (selected.includes(collectionId) || dismissed.includes(collectionId)) {
2984
+ continue;
2985
+ }
2986
+ const countStmt = this.db.prepare(
2987
+ "SELECT COUNT(*) as count FROM content WHERE collection_id = ?"
2988
+ );
2989
+ const countResult = await countStmt.bind(collectionId).first();
2990
+ const itemCount = countResult?.count || 0;
2991
+ notifications.push({
2992
+ collection: {
2993
+ id: collectionId,
2994
+ name: collection.name,
2995
+ display_name: collection.display_name,
2996
+ description: collection.description,
2997
+ item_count: itemCount,
2998
+ is_indexed: false,
2999
+ is_dismissed: false,
3000
+ is_new: true
3001
+ },
3002
+ message: `New collection "${collection.display_name}" with ${itemCount} items available for indexing`
3003
+ });
3004
+ }
3005
+ return notifications;
3006
+ } catch (error) {
3007
+ console.error("Error detecting new collections:", error);
3008
+ return [];
3009
+ }
3010
+ }
3011
+ /**
3012
+ * Get all collections with indexing status
3013
+ */
3014
+ async getAllCollections() {
3015
+ try {
3016
+ const collectionsStmt = this.db.prepare(
3017
+ "SELECT id, name, display_name, description FROM collections WHERE is_active = 1 ORDER BY display_name"
3018
+ );
3019
+ const { results: allCollections } = await collectionsStmt.all();
3020
+ console.log("[AISearchService.getAllCollections] Raw collections from DB:", allCollections?.length || 0);
3021
+ const firstCollection = allCollections?.[0];
3022
+ if (firstCollection) {
3023
+ console.log("[AISearchService.getAllCollections] Sample collection:", {
3024
+ id: firstCollection.id,
3025
+ name: firstCollection.name,
3026
+ display_name: firstCollection.display_name
3027
+ });
3028
+ }
3029
+ const collections2 = (allCollections || []).filter(
3030
+ (col) => col.id && col.name
3031
+ );
3032
+ console.log("[AISearchService.getAllCollections] After filtering test collections:", collections2.length);
3033
+ console.log("[AISearchService.getAllCollections] Remaining collections:", collections2.map((c) => c.name).join(", "));
3034
+ const settings = await this.getSettings();
3035
+ const selected = settings?.selected_collections || [];
3036
+ const dismissed = settings?.dismissed_collections || [];
3037
+ console.log("[AISearchService.getAllCollections] Settings:", {
3038
+ selected_count: selected.length,
3039
+ dismissed_count: dismissed.length,
3040
+ selected
3041
+ });
3042
+ const collectionInfos = [];
3043
+ for (const collection of collections2) {
3044
+ if (!collection.id || !collection.name) continue;
3045
+ const collectionId = String(collection.id);
3046
+ if (!collectionId) {
3047
+ console.warn("[AISearchService] Skipping invalid collection:", collection);
3048
+ continue;
3049
+ }
3050
+ const countStmt = this.db.prepare(
3051
+ "SELECT COUNT(*) as count FROM content WHERE collection_id = ?"
3052
+ );
3053
+ const countResult = await countStmt.bind(collectionId).first();
3054
+ const itemCount = countResult?.count || 0;
3055
+ collectionInfos.push({
3056
+ id: collectionId,
3057
+ name: collection.name,
3058
+ display_name: collection.display_name || collection.name,
3059
+ description: collection.description,
3060
+ item_count: itemCount,
3061
+ is_indexed: selected.includes(collectionId),
3062
+ is_dismissed: dismissed.includes(collectionId),
3063
+ is_new: !selected.includes(collectionId) && !dismissed.includes(collectionId)
3064
+ });
3065
+ }
3066
+ console.log("[AISearchService.getAllCollections] Returning collectionInfos:", collectionInfos.length);
3067
+ const firstInfo = collectionInfos[0];
3068
+ if (collectionInfos.length > 0 && firstInfo) {
3069
+ console.log("[AISearchService.getAllCollections] First collectionInfo:", {
3070
+ id: firstInfo.id,
3071
+ name: firstInfo.name,
3072
+ display_name: firstInfo.display_name,
3073
+ item_count: firstInfo.item_count
3074
+ });
3075
+ }
3076
+ return collectionInfos;
3077
+ } catch (error) {
3078
+ console.error("[AISearchService] Error fetching collections:", error);
3079
+ return [];
3080
+ }
3081
+ }
3082
+ /**
3083
+ * Execute search query
3084
+ */
3085
+ async search(query) {
3086
+ const settings = await this.getSettings();
3087
+ if (!settings?.enabled) {
3088
+ return {
3089
+ results: [],
3090
+ total: 0,
3091
+ query_time_ms: 0,
3092
+ mode: query.mode
3093
+ };
3094
+ }
3095
+ if (query.mode === "ai" && settings.ai_mode_enabled && this.customRAG?.isAvailable()) {
3096
+ return this.searchAI(query, settings);
3097
+ }
3098
+ return this.searchKeyword(query, settings);
3099
+ }
3100
+ /**
3101
+ * AI-powered semantic search using Custom RAG
3102
+ */
3103
+ async searchAI(query, settings) {
3104
+ try {
3105
+ if (!this.customRAG) {
3106
+ console.warn("[AISearchService] CustomRAG not available, falling back to keyword search");
3107
+ return this.searchKeyword(query, settings);
3108
+ }
3109
+ const result = await this.customRAG.search(query, settings);
3110
+ return result;
3111
+ } catch (error) {
3112
+ console.error("[AISearchService] AI search error, falling back to keyword:", error);
3113
+ return this.searchKeyword(query, settings);
3114
+ }
3115
+ }
3116
+ /**
3117
+ * Traditional keyword search
3118
+ */
3119
+ async searchKeyword(query, settings) {
3120
+ const startTime = Date.now();
3121
+ try {
3122
+ const conditions = [];
3123
+ const params = [];
3124
+ if (query.query) {
3125
+ conditions.push("(c.title LIKE ? OR c.slug LIKE ? OR c.data LIKE ?)");
3126
+ const searchTerm = `%${query.query}%`;
3127
+ params.push(searchTerm, searchTerm, searchTerm);
3128
+ }
3129
+ if (query.filters?.collections && query.filters.collections.length > 0) {
3130
+ const placeholders = query.filters.collections.map(() => "?").join(",");
3131
+ conditions.push(`c.collection_id IN (${placeholders})`);
3132
+ params.push(...query.filters.collections);
3133
+ } else if (settings.selected_collections.length > 0) {
3134
+ const placeholders = settings.selected_collections.map(() => "?").join(",");
3135
+ conditions.push(`c.collection_id IN (${placeholders})`);
3136
+ params.push(...settings.selected_collections);
3137
+ }
3138
+ if (query.filters?.status && query.filters.status.length > 0) {
3139
+ const placeholders = query.filters.status.map(() => "?").join(",");
3140
+ conditions.push(`c.status IN (${placeholders})`);
3141
+ params.push(...query.filters.status);
3142
+ } else {
3143
+ conditions.push("c.status != 'deleted'");
3144
+ }
3145
+ if (query.filters?.dateRange) {
3146
+ const field = query.filters.dateRange.field || "created_at";
3147
+ if (query.filters.dateRange.start) {
3148
+ conditions.push(`c.${field} >= ?`);
3149
+ params.push(query.filters.dateRange.start.getTime());
3150
+ }
3151
+ if (query.filters.dateRange.end) {
3152
+ conditions.push(`c.${field} <= ?`);
3153
+ params.push(query.filters.dateRange.end.getTime());
3154
+ }
3155
+ }
3156
+ if (query.filters?.author) {
3157
+ conditions.push("c.author_id = ?");
3158
+ params.push(query.filters.author);
3159
+ }
3160
+ const whereClause = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
3161
+ const countStmt = this.db.prepare(`
3162
+ SELECT COUNT(*) as count
3163
+ FROM content c
3164
+ ${whereClause}
3165
+ `);
3166
+ const countResult = await countStmt.bind(...params).first();
3167
+ const total = countResult?.count || 0;
3168
+ const limit = query.limit || settings.results_limit;
3169
+ const offset = query.offset || 0;
3170
+ const resultsStmt = this.db.prepare(`
3171
+ SELECT
3172
+ c.id, c.title, c.slug, c.collection_id, c.status,
3173
+ c.created_at, c.updated_at, c.author_id, c.data,
3174
+ col.name as collection_name, col.display_name as collection_display_name,
3175
+ u.email as author_email
3176
+ FROM content c
3177
+ JOIN collections col ON c.collection_id = col.id
3178
+ LEFT JOIN users u ON c.author_id = u.id
3179
+ ${whereClause}
3180
+ ORDER BY c.updated_at DESC
3181
+ LIMIT ? OFFSET ?
3182
+ `);
3183
+ const { results } = await resultsStmt.bind(...params, limit, offset).all();
3184
+ const searchResults = (results || []).map((row) => ({
3185
+ id: String(row.id),
3186
+ title: row.title || "Untitled",
3187
+ slug: row.slug || "",
3188
+ collection_id: String(row.collection_id),
3189
+ collection_name: row.collection_display_name || row.collection_name,
3190
+ snippet: this.extractSnippet(row.data, query.query),
3191
+ status: row.status,
3192
+ created_at: Number(row.created_at),
3193
+ updated_at: Number(row.updated_at),
3194
+ author_name: row.author_email
3195
+ }));
3196
+ const queryTime = Date.now() - startTime;
3197
+ await this.logSearch(query.query, query.mode, searchResults.length);
3198
+ return {
3199
+ results: searchResults,
3200
+ total,
3201
+ query_time_ms: queryTime,
3202
+ mode: query.mode
3203
+ };
3204
+ } catch (error) {
3205
+ console.error("Keyword search error:", error);
3206
+ return {
3207
+ results: [],
3208
+ total: 0,
3209
+ query_time_ms: Date.now() - startTime,
3210
+ mode: query.mode
3211
+ };
3212
+ }
3213
+ }
3214
+ /**
3215
+ * Extract snippet from content data
3216
+ */
3217
+ extractSnippet(data, query) {
3218
+ try {
3219
+ const parsed = typeof data === "string" ? JSON.parse(data) : data;
3220
+ const text = JSON.stringify(parsed).toLowerCase();
3221
+ const queryLower = query.toLowerCase();
3222
+ const index = text.indexOf(queryLower);
3223
+ if (index === -1) {
3224
+ return JSON.stringify(parsed).substring(0, 200) + "...";
3225
+ }
3226
+ const start = Math.max(0, index - 50);
3227
+ const end = Math.min(text.length, index + query.length + 50);
3228
+ return text.substring(start, end) + "...";
3229
+ } catch {
3230
+ return data.substring(0, 200) + "...";
3231
+ }
3232
+ }
3233
+ /**
3234
+ * Get search suggestions (autocomplete)
3235
+ */
3236
+ async getSearchSuggestions(partial) {
3237
+ try {
3238
+ const settings = await this.getSettings();
3239
+ if (!settings?.autocomplete_enabled) {
3240
+ return [];
3241
+ }
3242
+ if (this.customRAG?.isAvailable()) {
3243
+ try {
3244
+ const aiSuggestions = await this.customRAG.getSuggestions(partial, 5);
3245
+ if (aiSuggestions.length > 0) {
3246
+ return aiSuggestions;
3247
+ }
3248
+ } catch (error) {
3249
+ console.error("[AISearchService] Error getting AI suggestions:", error);
3250
+ }
3251
+ }
3252
+ const stmt = this.db.prepare(`
3253
+ SELECT DISTINCT query
3254
+ FROM ai_search_history
3255
+ WHERE query LIKE ?
3256
+ ORDER BY created_at DESC
3257
+ LIMIT 10
3258
+ `);
3259
+ const { results } = await stmt.bind(`%${partial}%`).all();
3260
+ return (results || []).map((r) => r.query);
3261
+ } catch (error) {
3262
+ console.error("Error getting suggestions:", error);
3263
+ return [];
3264
+ }
3265
+ }
3266
+ /**
3267
+ * Log search query to history
3268
+ */
3269
+ async logSearch(query, mode, resultsCount) {
3270
+ try {
3271
+ const stmt = this.db.prepare(`
3272
+ INSERT INTO ai_search_history (query, mode, results_count, created_at)
3273
+ VALUES (?, ?, ?, ?)
3274
+ `);
3275
+ await stmt.bind(query, mode, resultsCount, Date.now()).run();
3276
+ } catch (error) {
3277
+ console.error("Error logging search:", error);
3278
+ }
3279
+ }
3280
+ /**
3281
+ * Get search analytics
3282
+ */
3283
+ async getSearchAnalytics() {
3284
+ try {
3285
+ const totalStmt = this.db.prepare(`
3286
+ SELECT COUNT(*) as count
3287
+ FROM ai_search_history
3288
+ WHERE created_at >= ?
3289
+ `);
3290
+ const thirtyDaysAgo = Date.now() - 30 * 24 * 60 * 60 * 1e3;
3291
+ const totalResult = await totalStmt.bind(thirtyDaysAgo).first();
3292
+ const modeStmt = this.db.prepare(`
3293
+ SELECT mode, COUNT(*) as count
3294
+ FROM ai_search_history
3295
+ WHERE created_at >= ?
3296
+ GROUP BY mode
3297
+ `);
3298
+ const { results: modeResults } = await modeStmt.bind(thirtyDaysAgo).all();
3299
+ const aiCount = modeResults?.find((r) => r.mode === "ai")?.count || 0;
3300
+ const keywordCount = modeResults?.find((r) => r.mode === "keyword")?.count || 0;
3301
+ const popularStmt = this.db.prepare(`
3302
+ SELECT query, COUNT(*) as count
3303
+ FROM ai_search_history
3304
+ WHERE created_at >= ?
3305
+ GROUP BY query
3306
+ ORDER BY count DESC
3307
+ LIMIT 10
3308
+ `);
3309
+ const { results: popularResults } = await popularStmt.bind(thirtyDaysAgo).all();
3310
+ return {
3311
+ total_queries: totalResult?.count || 0,
3312
+ ai_queries: aiCount,
3313
+ keyword_queries: keywordCount,
3314
+ popular_queries: (popularResults || []).map((r) => ({
3315
+ query: r.query,
3316
+ count: r.count
3317
+ })),
3318
+ average_query_time: 0
3319
+ // TODO: Track query times
3320
+ };
3321
+ } catch (error) {
3322
+ console.error("Error getting analytics:", error);
3323
+ return {
3324
+ total_queries: 0,
3325
+ ai_queries: 0,
3326
+ keyword_queries: 0,
3327
+ popular_queries: [],
3328
+ average_query_time: 0
3329
+ };
3330
+ }
3331
+ }
3332
+ /**
3333
+ * Verify Custom RAG is available
3334
+ */
3335
+ verifyBinding() {
3336
+ return this.customRAG?.isAvailable() ?? false;
3337
+ }
3338
+ /**
3339
+ * Get Custom RAG service instance (for indexer)
3340
+ */
3341
+ getCustomRAG() {
3342
+ return this.customRAG;
3343
+ }
3344
+ };
3345
+
3346
+ // src/plugins/core-plugins/ai-search-plugin/services/indexer.ts
3347
+ var IndexManager = class {
3348
+ constructor(db, ai, vectorize) {
3349
+ this.db = db;
3350
+ this.ai = ai;
3351
+ this.vectorize = vectorize;
3352
+ if (this.ai && this.vectorize) {
3353
+ this.customRAG = new CustomRAGService(db, ai, vectorize);
3354
+ console.log("[IndexManager] Custom RAG initialized");
3355
+ }
3356
+ }
3357
+ customRAG;
3358
+ /**
3359
+ * Index all content items within a collection using Custom RAG
3360
+ */
3361
+ async indexCollection(collectionId) {
3362
+ try {
3363
+ const collectionStmt = this.db.prepare(
3364
+ "SELECT id, name, display_name FROM collections WHERE id = ?"
3365
+ );
3366
+ const collection = await collectionStmt.bind(collectionId).first();
3367
+ if (!collection) {
3368
+ throw new Error(`Collection ${collectionId} not found`);
3369
+ }
3370
+ await this.updateIndexStatus(collectionId, {
3371
+ collection_id: collectionId,
3372
+ collection_name: collection.display_name,
3373
+ total_items: 0,
3374
+ indexed_items: 0,
3375
+ status: "indexing"
3376
+ });
3377
+ if (this.customRAG?.isAvailable()) {
3378
+ console.log(`[IndexManager] Using Custom RAG to index collection ${collectionId}`);
3379
+ const result = await this.customRAG.indexCollection(collectionId);
3380
+ const finalStatus = {
3381
+ collection_id: collectionId,
3382
+ collection_name: collection.display_name,
3383
+ total_items: result.total_items,
3384
+ indexed_items: result.indexed_chunks,
3385
+ last_sync_at: Date.now(),
3386
+ status: result.errors > 0 ? "error" : "completed",
3387
+ error_message: result.errors > 0 ? `${result.errors} errors during indexing` : void 0
3388
+ };
3389
+ await this.updateIndexStatus(collectionId, finalStatus);
3390
+ return finalStatus;
3391
+ }
3392
+ console.warn(`[IndexManager] Custom RAG not available, skipping indexing for ${collectionId}`);
3393
+ const fallbackStatus = {
3394
+ collection_id: collectionId,
3395
+ collection_name: collection.display_name,
3396
+ total_items: 0,
3397
+ indexed_items: 0,
3398
+ last_sync_at: Date.now(),
3399
+ status: "completed",
3400
+ error_message: "Custom RAG not available - using keyword search only"
3401
+ };
3402
+ await this.updateIndexStatus(collectionId, fallbackStatus);
3403
+ return fallbackStatus;
3404
+ } catch (error) {
3405
+ console.error(`[IndexManager] Error indexing collection ${collectionId}:`, error);
3406
+ const errorStatus = {
3407
+ collection_id: collectionId,
3408
+ collection_name: "Unknown",
3409
+ total_items: 0,
3410
+ indexed_items: 0,
3411
+ status: "error",
3412
+ error_message: error instanceof Error ? error.message : String(error)
3413
+ };
3414
+ await this.updateIndexStatus(collectionId, errorStatus);
3415
+ return errorStatus;
3416
+ }
3417
+ }
3418
+ /**
3419
+ * Index a single content item
3420
+ */
3421
+ async indexContentItem(item, collectionId) {
3422
+ try {
3423
+ let parsedData = {};
3424
+ try {
3425
+ parsedData = typeof item.data === "string" ? JSON.parse(item.data) : item.data;
3426
+ } catch {
3427
+ parsedData = {};
3428
+ }
3429
+ const document = {
3430
+ id: `content_${item.id}`,
3431
+ title: item.title || "Untitled",
3432
+ slug: item.slug || "",
3433
+ content: this.extractSearchableText(parsedData),
3434
+ metadata: {
3435
+ collection_id: collectionId,
3436
+ collection_name: item.collection_name,
3437
+ collection_display_name: item.collection_display_name,
3438
+ status: item.status,
3439
+ created_at: item.created_at,
3440
+ updated_at: item.updated_at,
3441
+ author_id: item.author_id
3442
+ }
3443
+ };
3444
+ console.log(`Indexed content item: ${item.id}`);
3445
+ } catch (error) {
3446
+ console.error(`Error indexing content item ${item.id}:`, error);
3447
+ throw error;
3448
+ }
3449
+ }
3450
+ /**
3451
+ * Extract searchable text from content data
3452
+ */
3453
+ extractSearchableText(data) {
3454
+ const parts = [];
3455
+ if (data.title) parts.push(String(data.title));
3456
+ if (data.name) parts.push(String(data.name));
3457
+ if (data.description) parts.push(String(data.description));
3458
+ if (data.content) parts.push(String(data.content));
3459
+ if (data.body) parts.push(String(data.body));
3460
+ if (data.text) parts.push(String(data.text));
3461
+ const extractStrings = (obj) => {
3462
+ if (typeof obj === "string") {
3463
+ parts.push(obj);
3464
+ } else if (Array.isArray(obj)) {
3465
+ obj.forEach(extractStrings);
3466
+ } else if (obj && typeof obj === "object") {
3467
+ Object.values(obj).forEach(extractStrings);
3468
+ }
3469
+ };
3470
+ extractStrings(data);
3471
+ return parts.join(" ");
3472
+ }
3473
+ /**
3474
+ * Update a single content item in the index
3475
+ */
3476
+ async updateIndex(collectionId, contentId) {
3477
+ try {
3478
+ const stmt = this.db.prepare(`
3479
+ SELECT
3480
+ c.id, c.title, c.slug, c.data, c.status,
3481
+ c.created_at, c.updated_at, c.author_id,
3482
+ col.name as collection_name, col.display_name as collection_display_name
3483
+ FROM content c
3484
+ JOIN collections col ON c.collection_id = col.id
3485
+ WHERE c.id = ? AND c.collection_id = ?
3486
+ `);
3487
+ const item = await stmt.bind(contentId, collectionId).first();
3488
+ if (!item) {
3489
+ throw new Error(`Content item ${contentId} not found`);
3490
+ }
3491
+ await this.indexContentItem(item, String(collectionId));
3492
+ const status = await this.getIndexStatus(String(collectionId));
3493
+ if (status) {
3494
+ await this.updateIndexStatus(String(collectionId), {
3495
+ ...status,
3496
+ last_sync_at: Date.now()
3497
+ });
3498
+ }
3499
+ } catch (error) {
3500
+ console.error(`Error updating index for content ${contentId}:`, error);
3501
+ throw error;
3502
+ }
3503
+ }
3504
+ /**
3505
+ * Remove a content item from the index using Custom RAG
3506
+ */
3507
+ async removeFromIndex(collectionId, contentId) {
3508
+ try {
3509
+ if (this.customRAG?.isAvailable()) {
3510
+ console.log(`[IndexManager] Removing content ${contentId} from index`);
3511
+ await this.customRAG.removeContentFromIndex(contentId);
3512
+ } else {
3513
+ console.warn(`[IndexManager] Custom RAG not available, skipping removal for ${contentId}`);
3514
+ }
3515
+ } catch (error) {
3516
+ console.error(`[IndexManager] Error removing content ${contentId} from index:`, error);
3517
+ throw error;
3518
+ }
3519
+ }
3520
+ /**
3521
+ * Get indexing status for a collection
3522
+ */
3523
+ async getIndexStatus(collectionId) {
3524
+ try {
3525
+ const stmt = this.db.prepare(
3526
+ "SELECT * FROM ai_search_index_meta WHERE collection_id = ?"
3527
+ );
3528
+ const result = await stmt.bind(collectionId).first();
3529
+ if (!result) {
3530
+ return null;
3531
+ }
3532
+ return {
3533
+ collection_id: String(result.collection_id),
3534
+ collection_name: result.collection_name,
3535
+ total_items: result.total_items,
3536
+ indexed_items: result.indexed_items,
3537
+ last_sync_at: result.last_sync_at,
3538
+ status: result.status,
3539
+ error_message: result.error_message
3540
+ };
3541
+ } catch (error) {
3542
+ console.error(`Error getting index status for collection ${collectionId}:`, error);
3543
+ return null;
3544
+ }
3545
+ }
3546
+ /**
3547
+ * Get indexing status for all collections
3548
+ */
3549
+ async getAllIndexStatus() {
3550
+ try {
3551
+ const stmt = this.db.prepare("SELECT * FROM ai_search_index_meta");
3552
+ const { results } = await stmt.all();
3553
+ const statusMap = {};
3554
+ for (const row of results || []) {
3555
+ const collectionId = String(row.collection_id);
3556
+ statusMap[collectionId] = {
3557
+ collection_id: collectionId,
3558
+ collection_name: row.collection_name,
3559
+ total_items: row.total_items,
3560
+ indexed_items: row.indexed_items,
3561
+ last_sync_at: row.last_sync_at,
3562
+ status: row.status,
3563
+ error_message: row.error_message
3564
+ };
3565
+ }
3566
+ return statusMap;
3567
+ } catch (error) {
3568
+ console.error("Error getting all index status:", error);
3569
+ return {};
3570
+ }
3571
+ }
3572
+ /**
3573
+ * Update index status in database
3574
+ */
3575
+ async updateIndexStatus(collectionId, status) {
3576
+ try {
3577
+ const checkStmt = this.db.prepare(
3578
+ "SELECT id FROM ai_search_index_meta WHERE collection_id = ?"
3579
+ );
3580
+ const existing = await checkStmt.bind(collectionId).first();
3581
+ if (existing) {
3582
+ const stmt = this.db.prepare(`
3583
+ UPDATE ai_search_index_meta
3584
+ SET collection_name = ?,
3585
+ total_items = ?,
3586
+ indexed_items = ?,
3587
+ last_sync_at = ?,
3588
+ status = ?,
3589
+ error_message = ?
3590
+ WHERE collection_id = ?
3591
+ `);
3592
+ await stmt.bind(
3593
+ status.collection_name,
3594
+ status.total_items,
3595
+ status.indexed_items,
3596
+ status.last_sync_at || null,
3597
+ status.status,
3598
+ status.error_message || null,
3599
+ String(collectionId)
3600
+ ).run();
3601
+ } else {
3602
+ const stmt = this.db.prepare(`
3603
+ INSERT INTO ai_search_index_meta (
3604
+ collection_id, collection_name, total_items, indexed_items,
3605
+ last_sync_at, status, error_message
3606
+ ) VALUES (?, ?, ?, ?, ?, ?, ?)
3607
+ `);
3608
+ await stmt.bind(
3609
+ String(status.collection_id),
3610
+ status.collection_name,
3611
+ status.total_items,
3612
+ status.indexed_items,
3613
+ status.last_sync_at || null,
3614
+ status.status,
3615
+ status.error_message || null
3616
+ ).run();
3617
+ }
3618
+ } catch (error) {
3619
+ console.error(`Error updating index status for collection ${collectionId}:`, error);
3620
+ throw error;
3621
+ }
3622
+ }
3623
+ /**
3624
+ * Sync all selected collections
3625
+ */
3626
+ async syncAll(selectedCollections) {
3627
+ for (const collectionId of selectedCollections) {
3628
+ try {
3629
+ await this.indexCollection(collectionId);
3630
+ } catch (error) {
3631
+ console.error(`Error syncing collection ${collectionId}:`, error);
3632
+ }
3633
+ }
3634
+ }
3635
+ };
3636
+
3637
+ // src/plugins/core-plugins/ai-search-plugin/components/settings-page.ts
3638
+ function renderSettingsPage(data) {
3639
+ const settings = data.settings || {
3640
+ enabled: false,
3641
+ ai_mode_enabled: true,
3642
+ selected_collections: [],
3643
+ dismissed_collections: [],
3644
+ autocomplete_enabled: true,
3645
+ cache_duration: 1,
3646
+ results_limit: 20,
3647
+ index_media: false
3648
+ };
3649
+ const selectedCollections = Array.isArray(settings.selected_collections) ? settings.selected_collections : [];
3650
+ const dismissedCollections = Array.isArray(settings.dismissed_collections) ? settings.dismissed_collections : [];
3651
+ const enabled = settings.enabled === true;
3652
+ const aiModeEnabled = settings.ai_mode_enabled !== false;
3653
+ const autocompleteEnabled = settings.autocomplete_enabled !== false;
3654
+ const indexMedia = settings.index_media === true;
3655
+ const selectedCollectionIds = new Set(selectedCollections.map((id) => String(id)));
3656
+ const dismissedCollectionIds = new Set(dismissedCollections.map((id) => String(id)));
3657
+ const collections2 = Array.isArray(data.collections) ? data.collections : [];
3658
+ console.log("[SettingsPage Template] Collections received:", collections2.length);
3659
+ if (collections2.length > 0) {
3660
+ console.log("[SettingsPage Template] First collection:", collections2[0]);
3661
+ }
3662
+ const content2 = `
3663
+ <div class="w-full px-4 sm:px-6 lg:px-8 py-6">
3664
+ <!-- Header with Back Button -->
3665
+ <div class="flex flex-col sm:flex-row sm:items-center sm:justify-between mb-6">
3666
+ <div>
3667
+ <h1 class="text-2xl/8 font-semibold text-zinc-950 dark:text-white sm:text-xl/8">\u{1F50D} AI Search Settings</h1>
3668
+ <p class="mt-2 text-sm/6 text-zinc-500 dark:text-zinc-400">
3669
+ Configure advanced search with Cloudflare AI Search. Select collections to index and manage search preferences.
3670
+ </p>
3671
+ </div>
3672
+ <div class="mt-4 sm:mt-0 sm:ml-16 sm:flex-none">
3673
+ <a href="/admin/plugins" class="inline-flex items-center justify-center rounded-lg bg-white dark:bg-zinc-800 px-3.5 py-2.5 text-sm font-semibold text-zinc-950 dark:text-white ring-1 ring-inset ring-zinc-950/10 dark:ring-white/10 hover:bg-zinc-50 dark:hover:bg-zinc-700 transition-colors shadow-sm">
3674
+ <svg class="-ml-0.5 mr-1.5 h-5 w-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
3675
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 19l-7-7m0 0l7-7m-7 7h18"/>
3676
+ </svg>
3677
+ Back to Plugins
3678
+ </a>
3679
+ </div>
3680
+ </div>
3681
+
3682
+
3683
+ <!-- Main Settings Card -->
3684
+ <div class="rounded-xl bg-white dark:bg-zinc-900 shadow-sm ring-1 ring-zinc-950/5 dark:ring-white/10 p-6 mb-6">
3685
+ <form id="settingsForm" class="space-y-6">
3686
+ <!-- Enable Search Section -->
3687
+ <div>
3688
+ <h2 class="text-xl font-semibold text-zinc-950 dark:text-white mb-4">\u{1F50D} Search Settings</h2>
3689
+ <div class="space-y-3">
3690
+ <div class="flex items-center gap-3 p-4 border border-indigo-200 bg-indigo-50 dark:bg-indigo-900/20 rounded-lg">
3691
+ <input type="checkbox" id="enabled" name="enabled" ${enabled ? "checked" : ""} class="w-5 h-5 rounded border-gray-300 text-indigo-600 focus:ring-indigo-500 cursor-pointer">
3692
+ <div class="flex-1">
3693
+ <label for="enabled" class="text-base font-semibold text-zinc-900 dark:text-white select-none cursor-pointer block">Enable AI Search</label>
3694
+ <p class="text-xs text-zinc-600 dark:text-zinc-400 mt-0.5">Turn on advanced search capabilities across your content</p>
3695
+ </div>
3696
+ </div>
3697
+
3698
+ <div class="flex items-center gap-3 p-4 border border-blue-200 bg-blue-50 dark:bg-blue-900/20 rounded-lg">
3699
+ <input type="checkbox" id="ai_mode_enabled" name="ai_mode_enabled" ${aiModeEnabled ? "checked" : ""} class="w-5 h-5 rounded border-gray-300 text-blue-600 focus:ring-blue-500 cursor-pointer">
3700
+ <div class="flex-1">
3701
+ <label for="ai_mode_enabled" class="text-base font-semibold text-zinc-900 dark:text-white select-none cursor-pointer block">\u{1F916} AI/Semantic Search</label>
3702
+ <p class="text-xs text-zinc-600 dark:text-zinc-400 mt-0.5">
3703
+ Enable natural language queries (requires Cloudflare Workers AI binding)
3704
+ <a href="https://developers.cloudflare.com/workers-ai/" target="_blank" class="text-blue-600 dark:text-blue-400 hover:underline ml-1">\u2192 Setup Guide</a>
3705
+ </p>
3706
+ <p class="text-xs text-amber-600 dark:text-amber-400 mt-1">
3707
+ \u26A0\uFE0F If AI binding unavailable, will fallback to keyword search
3708
+ </p>
3709
+ </div>
3710
+ </div>
3711
+ </div>
3712
+ </div>
3713
+
3714
+ <hr class="border-zinc-200 dark:border-zinc-800">
3715
+
3716
+ <!-- Collections Section -->
3717
+ <div>
3718
+ <div class="flex items-start justify-between mb-4">
3719
+ <div>
3720
+ <h2 class="text-xl font-semibold text-zinc-950 dark:text-white">\u{1F4DA} Collections to Index</h2>
3721
+ <p class="text-sm text-zinc-600 dark:text-zinc-400 mt-1">
3722
+ Select which content collections should be indexed and searchable. Only checked collections will be included in search results.
3723
+ </p>
3724
+ </div>
3725
+ </div>
3726
+ <div class="space-y-3 max-h-96 overflow-y-auto border-2 border-zinc-300 dark:border-zinc-700 rounded-lg p-4 bg-white dark:bg-zinc-800" id="collections-list">
3727
+ ${collections2.length === 0 ? '<p class="text-sm text-zinc-500 dark:text-zinc-400 p-4">No collections available. Create collections first.</p>' : collections2.map((collection) => {
3728
+ const collectionId = String(collection.id);
3729
+ const isChecked = selectedCollectionIds.has(collectionId);
3730
+ const isDismissed = dismissedCollectionIds.has(collectionId);
3731
+ const indexStatusMap = data.indexStatus || {};
3732
+ const status = indexStatusMap[collectionId];
3733
+ const isNew = collection.is_new === true && !isDismissed && !status;
3734
+ const statusBadge = status && isChecked ? `<span class="ml-2 px-2 py-1 text-xs rounded-full ${status.status === "completed" ? "bg-green-100 text-green-800 dark:bg-green-900/30 dark:text-green-300" : status.status === "indexing" ? "bg-blue-100 text-blue-800 dark:bg-blue-900/30 dark:text-blue-300" : status.status === "error" ? "bg-red-100 text-red-800 dark:bg-red-900/30 dark:text-red-300" : "bg-gray-100 text-gray-800 dark:bg-gray-800 dark:text-gray-300"}">${status.status}</span>` : "";
3735
+ return `<div class="flex items-start gap-3 p-3 rounded-lg border border-zinc-200 dark:border-zinc-700 ${isNew ? "bg-blue-50 dark:bg-blue-900/10 border-blue-200 dark:border-blue-800" : "hover:bg-zinc-50 dark:hover:bg-zinc-800"}">
3736
+ <input
3737
+ type="checkbox"
3738
+ id="collection_${collectionId}"
3739
+ name="selected_collections"
3740
+ value="${collectionId}"
3741
+ ${isChecked ? "checked" : ""}
3742
+ class="mt-1 w-5 h-5 text-indigo-600 bg-white border-gray-300 rounded focus:ring-indigo-500 focus:ring-2 cursor-pointer"
3743
+ style="cursor: pointer; flex-shrink: 0;"
3744
+ />
3745
+ <div class="flex-1 min-w-0">
3746
+ <label for="collection_${collectionId}" class="text-sm font-medium text-zinc-950 dark:text-white select-none cursor-pointer flex items-center">
3747
+ ${collection.display_name || collection.name || "Unnamed Collection"}
3748
+ ${isNew ? '<span class="ml-2 px-2 py-0.5 text-xs rounded-full bg-blue-100 text-blue-800 dark:bg-blue-900/30 dark:text-blue-300">NEW</span>' : ""}
3749
+ ${statusBadge}
3750
+ </label>
3751
+ <p class="text-xs text-zinc-500 dark:text-zinc-400 mt-1">
3752
+ ${collection.description || collection.name || "No description"} \u2022 ${collection.item_count || 0} items
3753
+ ${status ? ` \u2022 ${status.indexed_items}/${status.total_items} indexed` : ""}
3754
+ </p>
3755
+ ${status && status.status === "indexing" ? `<div class="mt-2 w-full bg-gray-200 rounded-full h-2 dark:bg-gray-700">
3756
+ <div class="bg-blue-600 h-2 rounded-full" style="width: ${status.indexed_items / status.total_items * 100}%"></div>
3757
+ </div>` : ""}
3758
+ </div>
3759
+ ${isChecked ? `
3760
+ <button
3761
+ type="button"
3762
+ onclick="reindexCollection('${collectionId}')"
3763
+ class="px-3 py-1.5 text-xs font-medium text-white bg-indigo-600 hover:bg-indigo-700 rounded-md transition-colors flex items-center gap-1.5 whitespace-nowrap"
3764
+ ${status && status.status === "indexing" ? "disabled" : ""}
3765
+ >
3766
+ <svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
3767
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" />
3768
+ </svg>
3769
+ Re-index
3770
+ </button>
3771
+ ` : ""}
3772
+ </div>`;
3773
+ }).join("")}
3774
+ </div>
3775
+ </div>
3776
+
3777
+ <hr class="border-zinc-200 dark:border-zinc-800">
3778
+
3779
+ <!-- Advanced Options -->
3780
+ <div>
3781
+ <h2 class="text-xl font-semibold text-zinc-950 dark:text-white mb-4">\u2699\uFE0F Advanced Options</h2>
3782
+ <div class="grid grid-cols-1 md:grid-cols-2 gap-4">
3783
+ <div class="flex items-start gap-3 p-3 border border-zinc-200 dark:border-zinc-700 rounded-lg">
3784
+ <input type="checkbox" id="autocomplete_enabled" name="autocomplete_enabled" ${autocompleteEnabled ? "checked" : ""} class="mt-0.5 w-5 h-5 rounded border-gray-300 text-indigo-600 focus:ring-indigo-500 cursor-pointer">
3785
+ <div>
3786
+ <label for="autocomplete_enabled" class="text-sm font-medium text-zinc-950 dark:text-white select-none cursor-pointer block">Autocomplete Suggestions</label>
3787
+ <p class="text-xs text-zinc-500 dark:text-zinc-400 mt-0.5">Show search suggestions as users type</p>
3788
+ </div>
3789
+ </div>
3790
+
3791
+ <div class="flex items-start gap-3 p-3 border border-zinc-200 dark:border-zinc-700 rounded-lg">
3792
+ <input type="checkbox" id="index_media" name="index_media" ${indexMedia ? "checked" : ""} class="mt-0.5 w-5 h-5 rounded border-gray-300 text-indigo-600 focus:ring-indigo-500 cursor-pointer">
3793
+ <div>
3794
+ <label for="index_media" class="text-sm font-medium text-zinc-950 dark:text-white select-none cursor-pointer block">Index Media Metadata</label>
3795
+ <p class="text-xs text-zinc-500 dark:text-zinc-400 mt-0.5">Include media files in search results</p>
3796
+ </div>
3797
+ </div>
3798
+
3799
+ <div>
3800
+ <label class="block text-sm font-medium text-zinc-950 dark:text-white mb-2">Cache Duration (hours)</label>
3801
+ <input type="number" id="cache_duration" name="cache_duration" value="${settings.cache_duration || 1}" min="0" max="24" class="w-full rounded-lg bg-white dark:bg-white/5 px-3 py-2 text-sm text-zinc-950 dark:text-white ring-1 ring-inset ring-zinc-950/10 dark:ring-white/10 focus:ring-2 focus:ring-indigo-500">
3802
+ </div>
3803
+ <div>
3804
+ <label class="block text-sm font-medium text-zinc-950 dark:text-white mb-2">Results Per Page</label>
3805
+ <input type="number" id="results_limit" name="results_limit" value="${settings.results_limit || 20}" min="10" max="100" class="w-full rounded-lg bg-white dark:bg-white/5 px-3 py-2 text-sm text-zinc-950 dark:text-white ring-1 ring-inset ring-zinc-950/10 dark:ring-white/10 focus:ring-2 focus:ring-indigo-500">
3806
+ </div>
3807
+ </div>
3808
+ </div>
3809
+
3810
+ <!-- Save Button -->
3811
+ <div class="flex items-center justify-between pt-4 border-t border-zinc-200 dark:border-zinc-800">
3812
+ <p class="text-xs text-zinc-500 dark:text-zinc-400">
3813
+ \u{1F4A1} Collections marked as <span class="px-1.5 py-0.5 text-xs font-medium rounded-full bg-blue-500 text-white">NEW</span> haven't been indexed yet
3814
+ </p>
3815
+ <button type="submit" class="inline-flex items-center justify-center rounded-lg bg-indigo-600 text-white px-6 py-2.5 text-sm font-semibold hover:bg-indigo-500 shadow-sm transition-colors">
3816
+ \u{1F4BE} Save Settings
3817
+ </button>
3818
+ </div>
3819
+ </form>
3820
+ </div>
3821
+
3822
+
3823
+ <!-- Search Analytics -->
3824
+ <div class="rounded-xl bg-white dark:bg-zinc-900 shadow-sm ring-1 ring-zinc-950/5 dark:ring-white/10 p-6">
3825
+ <h2 class="text-xl font-semibold text-zinc-950 dark:text-white mb-4">\u{1F4CA} Search Analytics</h2>
3826
+ <div class="grid grid-cols-1 md:grid-cols-3 gap-4 mb-6">
3827
+ <div class="p-4 rounded-lg bg-zinc-50 dark:bg-zinc-800">
3828
+ <div class="text-sm text-zinc-500 dark:text-zinc-400">Total Queries</div>
3829
+ <div class="text-2xl font-bold text-zinc-950 dark:text-white mt-1">${data.analytics.total_queries}</div>
3830
+ </div>
3831
+ <div class="p-4 rounded-lg bg-zinc-50 dark:bg-zinc-800">
3832
+ <div class="text-sm text-zinc-500 dark:text-zinc-400">AI Queries</div>
3833
+ <div class="text-2xl font-bold text-blue-600 dark:text-blue-400 mt-1">${data.analytics.ai_queries}</div>
3834
+ </div>
3835
+ <div class="p-4 rounded-lg bg-zinc-50 dark:bg-zinc-800">
3836
+ <div class="text-sm text-zinc-500 dark:text-zinc-400">Keyword Queries</div>
3837
+ <div class="text-2xl font-bold text-indigo-600 dark:text-indigo-400 mt-1">${data.analytics.keyword_queries}</div>
3838
+ </div>
3839
+ </div>
3840
+ ${data.analytics.popular_queries.length > 0 ? `
3841
+ <div>
3842
+ <h3 class="text-sm font-semibold text-zinc-950 dark:text-white mb-2">Popular Searches</h3>
3843
+ <div class="space-y-1">
3844
+ ${data.analytics.popular_queries.map(
3845
+ (item) => `
3846
+ <div class="flex items-center justify-between text-sm">
3847
+ <span class="text-zinc-700 dark:text-zinc-300">"${item.query}"</span>
3848
+ <span class="text-zinc-500 dark:text-zinc-400">${item.count} times</span>
3849
+ </div>
3850
+ `
3851
+ ).join("")}
3852
+ </div>
3853
+ </div>
3854
+ ` : '<p class="text-sm text-zinc-500 dark:text-zinc-400">No search history yet.</p>'}
3855
+ </div>
3856
+
3857
+ <!-- Success Message -->
3858
+ <div id="msg" class="hidden fixed bottom-4 right-4 p-4 rounded-lg bg-green-50 text-green-900 border border-green-200 dark:bg-green-900/20 dark:text-green-100 dark:border-green-800 shadow-lg z-50">
3859
+ <div class="flex items-center gap-2">
3860
+ <span class="text-xl">\u2705</span>
3861
+ <span class="font-semibold">Settings Saved Successfully!</span>
3862
+ </div>
3863
+ </div>
3864
+ </div>
3865
+ <script>
3866
+ // Form submission with error handling
3867
+ document.getElementById('settingsForm').addEventListener('submit', async (e) => {
3868
+ e.preventDefault();
3869
+ console.log('[AI Search Client] Form submitted');
3870
+
3871
+ try {
3872
+ const btn = e.submitter;
3873
+ btn.innerText = 'Saving...';
3874
+ btn.disabled = true;
3875
+
3876
+ const formData = new FormData(e.target);
3877
+ const selectedCollections = Array.from(formData.getAll('selected_collections')).map(String);
3878
+
3879
+ const data = {
3880
+ enabled: document.getElementById('enabled').checked,
3881
+ ai_mode_enabled: document.getElementById('ai_mode_enabled').checked,
3882
+ selected_collections: selectedCollections,
3883
+ autocomplete_enabled: document.getElementById('autocomplete_enabled').checked,
3884
+ cache_duration: Number(formData.get('cache_duration')),
3885
+ results_limit: Number(formData.get('results_limit')),
3886
+ index_media: document.getElementById('index_media').checked,
3887
+ };
3888
+
3889
+ console.log('[AI Search Client] Sending data:', data);
3890
+ console.log('[AI Search Client] Selected collections:', selectedCollections);
3891
+
3892
+ const res = await fetch('/admin/plugins/ai-search', {
3893
+ method: 'POST',
3894
+ headers: {'Content-Type': 'application/json'},
3895
+ body: JSON.stringify(data)
3896
+ });
3897
+
3898
+ console.log('[AI Search Client] Response status:', res.status);
3899
+
3900
+ if (res.ok) {
3901
+ const result = await res.json();
3902
+ console.log('[AI Search Client] Save successful:', result);
3903
+ document.getElementById('msg').classList.remove('hidden');
3904
+ setTimeout(() => {
3905
+ document.getElementById('msg').classList.add('hidden');
3906
+ location.reload();
3907
+ }, 2000);
3908
+ } else {
3909
+ const error = await res.text();
3910
+ console.error('[AI Search Client] Save failed:', error);
3911
+ alert('Failed to save settings: ' + error);
3912
+ }
3913
+
3914
+ btn.innerText = 'Save Settings';
3915
+ btn.disabled = false;
3916
+ } catch (error) {
3917
+ console.error('[AI Search Client] Error:', error);
3918
+ alert('Error saving settings: ' + error.message);
3919
+ }
3920
+ });
3921
+
3922
+ // Add collection to index
3923
+ async function addCollectionToIndex(collectionId) {
3924
+ const form = document.getElementById('settingsForm');
3925
+ const checkbox = document.getElementById('collection_' + collectionId);
3926
+ if (checkbox) {
3927
+ checkbox.checked = true;
3928
+ form.dispatchEvent(new Event('submit'));
3929
+ }
3930
+ }
3931
+
3932
+ // Dismiss collection
3933
+ async function dismissCollection(collectionId) {
3934
+ const res = await fetch('/admin/plugins/ai-search', {
3935
+ method: 'POST',
3936
+ headers: {'Content-Type': 'application/json'},
3937
+ body: JSON.stringify({
3938
+ dismissed_collections: [collectionId]
3939
+ })
3940
+ });
3941
+ if (res.ok) {
3942
+ location.reload();
3943
+ }
3944
+ }
3945
+
3946
+ // Re-index collection
3947
+ async function reindexCollection(collectionId) {
3948
+ const res = await fetch('/admin/plugins/ai-search/api/reindex', {
3949
+ method: 'POST',
3950
+ headers: {'Content-Type': 'application/json'},
3951
+ body: JSON.stringify({ collection_id: collectionId })
3952
+ });
3953
+ if (res.ok) {
3954
+ alert('Re-indexing started. Page will refresh in a moment.');
3955
+ setTimeout(() => location.reload(), 2000);
3956
+ } else {
3957
+ alert('Failed to start re-indexing. Please try again.');
3958
+ }
3959
+ }
3960
+
3961
+ // Poll for index status updates
3962
+ setInterval(async () => {
3963
+ const res = await fetch('/admin/plugins/ai-search/api/status');
3964
+ if (res.ok) {
3965
+ const { data } = await res.json();
3966
+ // Update status indicators if needed
3967
+ // For now, just reload every 30 seconds if indexing is in progress
3968
+ const hasIndexing = Object.values(data).some((s) => s.status === 'indexing');
3969
+ if (hasIndexing) {
3970
+ location.reload();
3971
+ }
3972
+ }
3973
+ }, 30000);
3974
+ </script>
3975
+ `;
3976
+ return chunkBZC4FYW7_cjs.renderAdminLayout({
3977
+ title: "AI Search Settings",
3978
+ pageTitle: "AI Search Settings",
3979
+ currentPath: "/admin/plugins/ai-search/settings",
3980
+ user: data.user,
3981
+ content: content2
3982
+ });
3983
+ }
3984
+
3985
+ // src/plugins/core-plugins/ai-search-plugin/routes/admin.ts
3986
+ var adminRoutes = new hono.Hono();
3987
+ adminRoutes.use("*", chunkT3YIKW2A_cjs.requireAuth());
3988
+ adminRoutes.get("/", async (c) => {
3989
+ try {
3990
+ const user = c.get("user");
3991
+ const db = c.env.DB;
3992
+ const ai = c.env.AI;
3993
+ const vectorize = c.env.VECTORIZE_INDEX;
3994
+ const service = new AISearchService(db, ai, vectorize);
3995
+ const indexer = new IndexManager(db, ai, vectorize);
3996
+ const settings = await service.getSettings();
3997
+ console.log("[AI Search Settings Route] Settings loaded:", !!settings);
3998
+ const collections2 = await service.getAllCollections();
3999
+ console.log("[AI Search Settings Route] Collections returned:", collections2.length);
4000
+ if (collections2.length === 0) {
4001
+ const directQuery = await db.prepare("SELECT id, name, display_name FROM collections WHERE is_active = 1").all();
4002
+ console.log("[AI Search Settings Route] Direct DB query found:", directQuery.results?.length || 0, "collections");
4003
+ if (directQuery.results && directQuery.results.length > 0) {
4004
+ console.log("[AI Search Settings Route] Sample from DB:", directQuery.results[0]);
4005
+ }
4006
+ } else if (collections2.length > 0 && collections2[0]) {
4007
+ console.log("[AI Search Settings Route] First collection:", {
4008
+ id: collections2[0].id,
4009
+ name: collections2[0].name,
4010
+ display_name: collections2[0].display_name
4011
+ });
4012
+ }
4013
+ const newCollections = await service.detectNewCollections();
4014
+ console.log("AI Search: New collections:", newCollections.length);
4015
+ const indexStatus = await indexer.getAllIndexStatus();
4016
+ console.log("AI Search: Index status:", Object.keys(indexStatus).length);
4017
+ const analytics = await service.getSearchAnalytics();
4018
+ return c.html(
4019
+ renderSettingsPage({
4020
+ settings,
4021
+ collections: collections2 || [],
4022
+ newCollections: newCollections || [],
4023
+ indexStatus: indexStatus || {},
4024
+ analytics,
4025
+ user: {
4026
+ name: user.email,
4027
+ email: user.email,
4028
+ role: user.role
4029
+ }
4030
+ })
4031
+ );
4032
+ } catch (error) {
4033
+ console.error("Error rendering AI Search settings:", error);
4034
+ return c.html(`<p>Error loading settings: ${error instanceof Error ? error.message : String(error)}</p>`, 500);
4035
+ }
4036
+ });
4037
+ adminRoutes.post("/", async (c) => {
4038
+ try {
4039
+ const db = c.env.DB;
4040
+ const ai = c.env.AI;
4041
+ const vectorize = c.env.VECTORIZE_INDEX;
4042
+ const service = new AISearchService(db, ai, vectorize);
4043
+ const indexer = new IndexManager(db, ai, vectorize);
4044
+ const body = await c.req.json();
4045
+ console.log("[AI Search POST] Received body:", JSON.stringify(body, null, 2));
4046
+ const currentSettings = await service.getSettings();
4047
+ console.log("[AI Search POST] Current settings selected_collections:", currentSettings?.selected_collections);
4048
+ const updatedSettings = {
4049
+ enabled: body.enabled !== void 0 ? Boolean(body.enabled) : currentSettings?.enabled,
4050
+ ai_mode_enabled: body.ai_mode_enabled !== void 0 ? Boolean(body.ai_mode_enabled) : currentSettings?.ai_mode_enabled,
4051
+ selected_collections: Array.isArray(body.selected_collections) ? body.selected_collections.map(String) : currentSettings?.selected_collections || [],
4052
+ dismissed_collections: Array.isArray(body.dismissed_collections) ? body.dismissed_collections.map(String) : currentSettings?.dismissed_collections || [],
4053
+ autocomplete_enabled: body.autocomplete_enabled !== void 0 ? Boolean(body.autocomplete_enabled) : currentSettings?.autocomplete_enabled,
4054
+ cache_duration: body.cache_duration ? Number(body.cache_duration) : currentSettings?.cache_duration,
4055
+ results_limit: body.results_limit ? Number(body.results_limit) : currentSettings?.results_limit,
4056
+ index_media: body.index_media !== void 0 ? Boolean(body.index_media) : currentSettings?.index_media
4057
+ };
4058
+ console.log("[AI Search POST] Updated settings selected_collections:", updatedSettings.selected_collections);
4059
+ const collectionsChanged = JSON.stringify(updatedSettings.selected_collections) !== JSON.stringify(currentSettings?.selected_collections || []);
4060
+ const saved = await service.updateSettings(updatedSettings);
4061
+ console.log("[AI Search POST] Settings saved, selected_collections:", saved.selected_collections);
4062
+ if (collectionsChanged && updatedSettings.selected_collections) {
4063
+ console.log("[AI Search POST] Collections changed, starting background indexing");
4064
+ c.executionCtx.waitUntil(
4065
+ indexer.syncAll(updatedSettings.selected_collections).then(() => console.log("[AI Search POST] Background indexing completed")).catch((error) => console.error("[AI Search POST] Background indexing error:", error))
4066
+ );
4067
+ }
4068
+ return c.json({ success: true, settings: saved });
4069
+ } catch (error) {
4070
+ console.error("Error updating AI Search settings:", error);
4071
+ return c.json({ error: "Failed to update settings" }, 500);
4072
+ }
4073
+ });
4074
+ adminRoutes.get("/api/settings", async (c) => {
4075
+ try {
4076
+ const db = c.env.DB;
4077
+ const ai = c.env.AI;
4078
+ const vectorize = c.env.VECTORIZE_INDEX;
4079
+ const service = new AISearchService(db, ai, vectorize);
4080
+ const settings = await service.getSettings();
4081
+ return c.json({ success: true, data: settings });
4082
+ } catch (error) {
4083
+ console.error("Error fetching settings:", error);
4084
+ return c.json({ error: "Failed to fetch settings" }, 500);
4085
+ }
4086
+ });
4087
+ adminRoutes.get("/api/new-collections", async (c) => {
4088
+ try {
4089
+ const db = c.env.DB;
4090
+ const ai = c.env.AI;
4091
+ const vectorize = c.env.VECTORIZE_INDEX;
4092
+ const service = new AISearchService(db, ai, vectorize);
4093
+ const notifications = await service.detectNewCollections();
4094
+ return c.json({ success: true, data: notifications });
4095
+ } catch (error) {
4096
+ console.error("Error detecting new collections:", error);
4097
+ return c.json({ error: "Failed to detect new collections" }, 500);
4098
+ }
4099
+ });
4100
+ adminRoutes.get("/api/status", async (c) => {
4101
+ try {
4102
+ const db = c.env.DB;
4103
+ const ai = c.env.AI;
4104
+ const vectorize = c.env.VECTORIZE_INDEX;
4105
+ const indexer = new IndexManager(db, ai, vectorize);
4106
+ const status = await indexer.getAllIndexStatus();
4107
+ return c.json({ success: true, data: status });
4108
+ } catch (error) {
4109
+ console.error("Error fetching index status:", error);
4110
+ return c.json({ error: "Failed to fetch status" }, 500);
4111
+ }
4112
+ });
4113
+ adminRoutes.post("/api/reindex", async (c) => {
4114
+ try {
4115
+ const db = c.env.DB;
4116
+ const ai = c.env.AI;
4117
+ const vectorize = c.env.VECTORIZE_INDEX;
4118
+ const indexer = new IndexManager(db, ai, vectorize);
4119
+ const body = await c.req.json();
4120
+ const collectionIdRaw = body.collection_id;
4121
+ const collectionId = collectionIdRaw ? String(collectionIdRaw) : "";
4122
+ if (!collectionId || collectionId === "undefined" || collectionId === "null") {
4123
+ return c.json({ error: "collection_id is required" }, 400);
4124
+ }
4125
+ c.executionCtx.waitUntil(
4126
+ indexer.indexCollection(collectionId).then(() => console.log(`[AI Search Reindex] Completed for collection ${collectionId}`)).catch((error) => console.error(`[AI Search Reindex] Error for collection ${collectionId}:`, error))
4127
+ );
4128
+ return c.json({ success: true, message: "Re-indexing started" });
4129
+ } catch (error) {
4130
+ console.error("Error starting re-index:", error);
4131
+ return c.json({ error: "Failed to start re-indexing" }, 500);
4132
+ }
4133
+ });
4134
+ var admin_default = adminRoutes;
4135
+ var apiRoutes = new hono.Hono();
4136
+ apiRoutes.post("/", async (c) => {
4137
+ try {
4138
+ const db = c.env.DB;
4139
+ const ai = c.env.AI;
4140
+ const vectorize = c.env.VECTORIZE_INDEX;
4141
+ const service = new AISearchService(db, ai, vectorize);
4142
+ const body = await c.req.json();
4143
+ const query = {
4144
+ query: body.query || "",
4145
+ mode: body.mode || "keyword",
4146
+ filters: body.filters || {},
4147
+ limit: body.limit ? Number(body.limit) : void 0,
4148
+ offset: body.offset ? Number(body.offset) : void 0
4149
+ };
4150
+ if (query.filters?.dateRange) {
4151
+ if (typeof query.filters.dateRange.start === "string") {
4152
+ query.filters.dateRange.start = new Date(query.filters.dateRange.start);
4153
+ }
4154
+ if (typeof query.filters.dateRange.end === "string") {
4155
+ query.filters.dateRange.end = new Date(query.filters.dateRange.end);
4156
+ }
4157
+ }
4158
+ const results = await service.search(query);
4159
+ return c.json({
4160
+ success: true,
4161
+ data: results
4162
+ });
4163
+ } catch (error) {
4164
+ console.error("Search error:", error);
4165
+ return c.json(
4166
+ {
4167
+ success: false,
4168
+ error: "Search failed",
4169
+ message: error instanceof Error ? error.message : String(error)
4170
+ },
4171
+ 500
4172
+ );
4173
+ }
4174
+ });
4175
+ apiRoutes.get("/suggest", async (c) => {
4176
+ try {
4177
+ const db = c.env.DB;
4178
+ const ai = c.env.AI;
4179
+ const vectorize = c.env.VECTORIZE_INDEX;
4180
+ const service = new AISearchService(db, ai, vectorize);
4181
+ const query = c.req.query("q") || "";
4182
+ if (!query || query.length < 2) {
4183
+ return c.json({ success: true, data: [] });
4184
+ }
4185
+ const suggestions = await service.getSearchSuggestions(query);
4186
+ return c.json({
4187
+ success: true,
4188
+ data: suggestions
4189
+ });
4190
+ } catch (error) {
4191
+ console.error("Suggestions error:", error);
4192
+ return c.json(
4193
+ {
4194
+ success: false,
4195
+ error: "Failed to get suggestions"
4196
+ },
4197
+ 500
4198
+ );
4199
+ }
4200
+ });
4201
+ apiRoutes.get("/analytics", async (c) => {
4202
+ try {
4203
+ const db = c.env.DB;
4204
+ const ai = c.env.AI;
4205
+ const vectorize = c.env.VECTORIZE_INDEX;
4206
+ const service = new AISearchService(db, ai, vectorize);
4207
+ const analytics = await service.getSearchAnalytics();
4208
+ return c.json({
4209
+ success: true,
4210
+ data: analytics
4211
+ });
4212
+ } catch (error) {
4213
+ console.error("Analytics error:", error);
4214
+ return c.json(
4215
+ {
4216
+ success: false,
4217
+ error: "Failed to get analytics"
4218
+ },
4219
+ 500
4220
+ );
4221
+ }
4222
+ });
4223
+ var api_default2 = apiRoutes;
4224
+
4225
+ // src/plugins/core-plugins/ai-search-plugin/manifest.json
4226
+ var manifest_default = {
4227
+ name: "AI Search",
4228
+ description: "Advanced search with Cloudflare AI Search. Full-text search, semantic search, and advanced filtering across all content collections.",
4229
+ version: "1.0.0",
4230
+ author: "SonicJS"};
4231
+
4232
+ // src/plugins/core-plugins/ai-search-plugin/index.ts
4233
+ var aiSearchPlugin = new chunkYHW27CBV_cjs.PluginBuilder({
4234
+ name: manifest_default.name,
4235
+ version: manifest_default.version,
4236
+ description: manifest_default.description,
4237
+ author: { name: manifest_default.author }
4238
+ }).metadata({
4239
+ description: manifest_default.description,
4240
+ author: { name: manifest_default.author }
4241
+ }).addService("aiSearch", AISearchService).addService("indexManager", IndexManager).addRoute("/admin/plugins/ai-search", admin_default).addRoute("/api/search", api_default2).build();
2432
4242
  var magicLinkRequestSchema = zod.z.object({
2433
4243
  email: zod.z.string().email("Valid email is required")
2434
4244
  });
@@ -2575,12 +4385,12 @@ function createMagicLinkAuthPlugin() {
2575
4385
  SET used = 1, used_at = ?
2576
4386
  WHERE id = ?
2577
4387
  `).bind(Date.now(), magicLink.id).run();
2578
- const jwtToken = await chunk7I5INVNR_cjs.AuthManager.generateToken(
4388
+ const jwtToken = await chunkT3YIKW2A_cjs.AuthManager.generateToken(
2579
4389
  user.id,
2580
4390
  user.email,
2581
4391
  user.role
2582
4392
  );
2583
- chunk7I5INVNR_cjs.AuthManager.setAuthCookie(c, jwtToken);
4393
+ chunkT3YIKW2A_cjs.AuthManager.setAuthCookie(c, jwtToken);
2584
4394
  await db.prepare(`
2585
4395
  UPDATE users SET last_login_at = ? WHERE id = ?
2586
4396
  `).bind(Date.now(), user.id).run();
@@ -2725,18 +4535,46 @@ function renderMagicLinkEmail(magicLink, expiryMinutes) {
2725
4535
  }
2726
4536
  createMagicLinkAuthPlugin();
2727
4537
 
4538
+ // src/assets/favicon.ts
4539
+ var faviconSvg = `<?xml version="1.0" encoding="UTF-8" standalone="no"?>
4540
+ <svg
4541
+ version="1.1"
4542
+ id="Layer_1"
4543
+ x="0px"
4544
+ y="0px"
4545
+ viewBox="380 1300 257.89001 278.8855"
4546
+ xml:space="preserve"
4547
+ width="257.89001"
4548
+ height="278.8855"
4549
+ xmlns="http://www.w3.org/2000/svg">
4550
+ <g
4551
+ id="g10"
4552
+ transform="translate(-383.935,-60.555509)">
4553
+ <g
4554
+ id="g9">
4555
+ <path
4556
+ fill="#f1f2f2"
4557
+ d="m 974.78,1398.211 c -5.016,6.574 -10.034,13.146 -15.048,19.721 -1.828,2.398 -3.657,4.796 -5.487,7.194 1.994,1.719 3.958,3.51 5.873,5.424 18.724,18.731 28.089,41.216 28.089,67.459 0,26.251 -9.366,48.658 -28.089,67.237 -18.731,18.579 -41.215,27.868 -67.459,27.868 -9.848,0 -19.156,-1.308 -27.923,-3.923 l -4.185,3.354 c -8.587,6.885 -17.154,13.796 -25.725,20.702 17.52,8.967 36.86,13.487 58.054,13.487 35.533,0 65.91,-12.608 91.124,-37.821 25.214,-25.215 37.821,-55.584 37.821,-91.125 0,-35.534 -12.607,-65.911 -37.821,-91.126 -3,-2.999 -6.078,-5.808 -9.224,-8.451 z"
4558
+ id="path2" />
4559
+ <path
4560
+ fill="#34d399"
4561
+ d="m 854.024,1585.195 20.001,-16.028 c 16.616,-13.507 33.04,-27.265 50.086,-40.251 1.13,-0.861 2.9,-1.686 2.003,-3.516 -0.843,-1.716 -2.481,-2.302 -4.484,-2.123 -8.514,0.765 -17.016,-0.538 -25.537,-0.353 -1.124,0.024 -2.768,0.221 -3.163,-1.25 -0.371,-1.369 1.088,-2.063 1.919,-2.894 6.26,-6.242 12.574,-12.43 18.816,-18.691 9.303,-9.327 18.565,-18.714 27.851,-28.066 1.848,-1.859 3.701,-3.713 5.549,-5.572 2.655,-2.661 5.309,-5.315 7.958,-7.982 0.574,-0.579 1.259,-1.141 1.246,-1.94 -0.004,-0.257 -0.078,-0.538 -0.254,-0.853 -0.556,-0.981 -1.441,-1.1 -2.469,-0.957 -0.658,0.096 -1.315,0.185 -1.973,0.275 -3.844,0.538 -7.689,1.076 -11.533,1.608 -3.641,0.505 -7.281,1.02 -10.922,1.529 -4.162,0.582 -8.324,1.158 -12.486,1.748 -1.142,0.161 -2.409,1.662 -3.354,0.508 -0.419,-0.508 -0.431,-1.028 -0.251,-1.531 0.269,-0.741 0.957,-1.441 1.387,-2.021 3.414,-4.58 6.882,-9.124 10.356,-13.662 1.74,-2.272 3.48,-4.544 5.214,-6.822 4.682,-6.141 9.369,-12.281 14.051,-18.422 0.09,-0.119 0.181,-0.237 0.271,-0.355 6.848,-8.98 13.7,-17.958 20.553,-26.936 0.488,-0.64 0.977,-1.28 1.465,-1.92 2.159,-2.828 4.315,-5.658 6.476,-8.486 4.197,-5.501 8.454,-10.954 12.67,-16.442 0.263,-0.347 0.538,-0.718 0.717,-1.106 0.269,-0.586 0.299,-1.196 -0.335,-1.776 -0.825,-0.753 -1.8,-0.15 -2.595,0.419 -0.67,0.472 -1.333,0.957 -1.955,1.489 -2.206,1.889 -4.401,3.797 -6.595,5.698 -3.958,3.438 -7.922,6.876 -11.976,10.194 -2.443,2.003 -4.865,4.028 -7.301,6.038 -18.689,-10.581 -39.53,-15.906 -62.549,-15.906 -35.54,0 -65.911,12.607 -91.125,37.82 -25.214,25.215 -37.821,55.592 -37.821,91.126 0,35.54 12.607,65.91 37.821,91.125 4.146,4.146 8.445,7.916 12.87,11.381 -9.015,11.14 -18.036,22.277 -27.034,33.429 -1.208,1.489 -3.755,3.151 -2.745,4.891 0.078,0.144 0.173,0.281 0.305,0.425 1.321,1.429 3.492,-1.303 4.933,-2.457 6.673,-5.333 13.333,-10.685 19.982,-16.042 3.707,-2.984 7.417,-5.965 11.124,-8.952 1.474,-1.188 2.951,-2.373 4.425,-3.561 6.41,-5.164 12.816,-10.333 19.238,-15.481 z m -56.472,-87.186 c 0,-26.243 9.29,-48.728 27.868,-67.459 18.579,-18.723 40.987,-28.089 67.238,-28.089 12.273,0 23.712,2.075 34.34,6.171 -3.37,2.905 -6.734,5.816 -10.069,8.762 -6.075,5.351 -12.365,10.469 -18.667,15.564 -4.179,3.378 -8.371,6.744 -12.514,10.164 -7.54,6.23 -15.037,12.52 -22.529,18.804 -7.091,5.955 -14.182,11.904 -21.19,17.949 -1.136,0.974 -3.055,1.907 -2.135,3.94 0.831,1.836 2.774,1.417 4.341,1.578 l 12.145,-0.599 14.151,-0.698 c 1.031,-0.102 2.192,-0.257 2.89,0.632 0.034,0.044 0.073,0.078 0.106,0.127 1.017,1.561 -0.67,2.105 -1.387,2.942 -6.308,7.318 -12.616,14.637 -18.978,21.907 -8.161,9.339 -16.353,18.649 -24.544,27.958 -2.146,2.433 -4.275,4.879 -6.422,7.312 -1.034,1.172 -2.129,2.272 -1.238,3.922 0.933,1.728 2.685,1.752 4.323,1.602 4.134,-0.367 8.263,-0.489 12.396,-0.492 0.242,0 0.485,-0.01 0.728,0 2.711,0.01 5.422,0.068 8.134,0.145 2.582,0.074 5.166,0.165 7.752,0.249 0.275,1.62 -0.879,2.356 -1.62,3.259 -1.333,1.626 -2.667,3.247 -4,4.867 -4.315,5.252 -8.62,10.514 -12.928,15.772 -3.562,-2.725 -7.007,-5.733 -10.324,-9.051 -18.577,-18.576 -27.867,-40.983 -27.867,-67.234 z"
4562
+ id="path9" />
4563
+ </g>
4564
+ </g>
4565
+ </svg>`;
4566
+
2728
4567
  // src/app.ts
2729
4568
  function createSonicJSApp(config = {}) {
2730
4569
  const app = new hono.Hono();
2731
- const appVersion = config.version || chunkFYEDK7K7_cjs.getCoreVersion();
4570
+ const appVersion = config.version || chunkYMTTGHEK_cjs.getCoreVersion();
2732
4571
  const appName = config.name || "SonicJS AI";
2733
4572
  app.use("*", async (c, next) => {
2734
4573
  c.set("appVersion", appVersion);
2735
4574
  await next();
2736
4575
  });
2737
- app.use("*", chunk7I5INVNR_cjs.metricsMiddleware());
2738
- app.use("*", chunk7I5INVNR_cjs.bootstrapMiddleware(config));
2739
- app.use("*", adminSetupMiddleware());
4576
+ app.use("*", chunkT3YIKW2A_cjs.metricsMiddleware());
4577
+ app.use("*", chunkT3YIKW2A_cjs.bootstrapMiddleware(config));
2740
4578
  if (config.middleware?.beforeAuth) {
2741
4579
  for (const middleware of config.middleware.beforeAuth) {
2742
4580
  app.use("*", middleware);
@@ -2753,22 +4591,27 @@ function createSonicJSApp(config = {}) {
2753
4591
  app.use("*", middleware);
2754
4592
  }
2755
4593
  }
2756
- app.route("/api", chunkA4SVOGG6_cjs.api_default);
2757
- app.route("/api/media", chunkA4SVOGG6_cjs.api_media_default);
2758
- app.route("/api/system", chunkA4SVOGG6_cjs.api_system_default);
2759
- app.route("/admin/api", chunkA4SVOGG6_cjs.admin_api_default);
2760
- app.route("/admin/dashboard", chunkA4SVOGG6_cjs.router);
2761
- app.route("/admin/collections", chunkA4SVOGG6_cjs.adminCollectionsRoutes);
2762
- app.route("/admin/settings", chunkA4SVOGG6_cjs.adminSettingsRoutes);
4594
+ app.route("/api", chunkN7TDLOUE_cjs.api_default);
4595
+ app.route("/api/media", chunkN7TDLOUE_cjs.api_media_default);
4596
+ app.route("/api/system", chunkN7TDLOUE_cjs.api_system_default);
4597
+ app.route("/admin/api", chunkN7TDLOUE_cjs.admin_api_default);
4598
+ app.route("/admin/dashboard", chunkN7TDLOUE_cjs.router);
4599
+ app.route("/admin/collections", chunkN7TDLOUE_cjs.adminCollectionsRoutes);
4600
+ app.route("/admin/settings", chunkN7TDLOUE_cjs.adminSettingsRoutes);
2763
4601
  app.route("/admin/database-tools", createDatabaseToolsAdminRoutes());
2764
4602
  app.route("/admin/seed-data", createSeedDataAdminRoutes());
2765
- app.route("/admin/content", chunkA4SVOGG6_cjs.admin_content_default);
2766
- app.route("/admin/media", chunkA4SVOGG6_cjs.adminMediaRoutes);
2767
- app.route("/admin/plugins", chunkA4SVOGG6_cjs.adminPluginRoutes);
2768
- app.route("/admin/logs", chunkA4SVOGG6_cjs.adminLogsRoutes);
2769
- app.route("/admin", chunkA4SVOGG6_cjs.userRoutes);
2770
- app.route("/auth", chunkA4SVOGG6_cjs.auth_default);
2771
- app.route("/", chunkA4SVOGG6_cjs.test_cleanup_default);
4603
+ app.route("/admin/content", chunkN7TDLOUE_cjs.admin_content_default);
4604
+ app.route("/admin/media", chunkN7TDLOUE_cjs.adminMediaRoutes);
4605
+ if (aiSearchPlugin.routes && aiSearchPlugin.routes.length > 0) {
4606
+ for (const route of aiSearchPlugin.routes) {
4607
+ app.route(route.path, route.handler);
4608
+ }
4609
+ }
4610
+ app.route("/admin/plugins", chunkN7TDLOUE_cjs.adminPluginRoutes);
4611
+ app.route("/admin/logs", chunkN7TDLOUE_cjs.adminLogsRoutes);
4612
+ app.route("/admin", chunkN7TDLOUE_cjs.userRoutes);
4613
+ app.route("/auth", chunkN7TDLOUE_cjs.auth_default);
4614
+ app.route("/", chunkN7TDLOUE_cjs.test_cleanup_default);
2772
4615
  if (emailPlugin.routes && emailPlugin.routes.length > 0) {
2773
4616
  for (const route of emailPlugin.routes) {
2774
4617
  app.route(route.path, route.handler);
@@ -2785,6 +4628,14 @@ function createSonicJSApp(config = {}) {
2785
4628
  app.route(route.path, route.handler);
2786
4629
  }
2787
4630
  }
4631
+ app.get("/favicon.svg", (c) => {
4632
+ return new Response(faviconSvg, {
4633
+ headers: {
4634
+ "Content-Type": "image/svg+xml",
4635
+ "Cache-Control": "public, max-age=31536000"
4636
+ }
4637
+ });
4638
+ });
2788
4639
  app.get("/files/*", async (c) => {
2789
4640
  try {
2790
4641
  const url = new URL(c.req.url);
@@ -2848,83 +4699,83 @@ function createDb(d1$1) {
2848
4699
  }
2849
4700
 
2850
4701
  // src/index.ts
2851
- var VERSION = chunkFYEDK7K7_cjs.package_default.version;
4702
+ var VERSION = chunkYMTTGHEK_cjs.package_default.version;
2852
4703
 
2853
4704
  Object.defineProperty(exports, "ROUTES_INFO", {
2854
4705
  enumerable: true,
2855
- get: function () { return chunkA4SVOGG6_cjs.ROUTES_INFO; }
4706
+ get: function () { return chunkN7TDLOUE_cjs.ROUTES_INFO; }
2856
4707
  });
2857
4708
  Object.defineProperty(exports, "adminApiRoutes", {
2858
4709
  enumerable: true,
2859
- get: function () { return chunkA4SVOGG6_cjs.admin_api_default; }
4710
+ get: function () { return chunkN7TDLOUE_cjs.admin_api_default; }
2860
4711
  });
2861
4712
  Object.defineProperty(exports, "adminCheckboxRoutes", {
2862
4713
  enumerable: true,
2863
- get: function () { return chunkA4SVOGG6_cjs.adminCheckboxRoutes; }
4714
+ get: function () { return chunkN7TDLOUE_cjs.adminCheckboxRoutes; }
2864
4715
  });
2865
4716
  Object.defineProperty(exports, "adminCodeExamplesRoutes", {
2866
4717
  enumerable: true,
2867
- get: function () { return chunkA4SVOGG6_cjs.admin_code_examples_default; }
4718
+ get: function () { return chunkN7TDLOUE_cjs.admin_code_examples_default; }
2868
4719
  });
2869
4720
  Object.defineProperty(exports, "adminCollectionsRoutes", {
2870
4721
  enumerable: true,
2871
- get: function () { return chunkA4SVOGG6_cjs.adminCollectionsRoutes; }
4722
+ get: function () { return chunkN7TDLOUE_cjs.adminCollectionsRoutes; }
2872
4723
  });
2873
4724
  Object.defineProperty(exports, "adminContentRoutes", {
2874
4725
  enumerable: true,
2875
- get: function () { return chunkA4SVOGG6_cjs.admin_content_default; }
4726
+ get: function () { return chunkN7TDLOUE_cjs.admin_content_default; }
2876
4727
  });
2877
4728
  Object.defineProperty(exports, "adminDashboardRoutes", {
2878
4729
  enumerable: true,
2879
- get: function () { return chunkA4SVOGG6_cjs.router; }
4730
+ get: function () { return chunkN7TDLOUE_cjs.router; }
2880
4731
  });
2881
4732
  Object.defineProperty(exports, "adminDesignRoutes", {
2882
4733
  enumerable: true,
2883
- get: function () { return chunkA4SVOGG6_cjs.adminDesignRoutes; }
4734
+ get: function () { return chunkN7TDLOUE_cjs.adminDesignRoutes; }
2884
4735
  });
2885
4736
  Object.defineProperty(exports, "adminLogsRoutes", {
2886
4737
  enumerable: true,
2887
- get: function () { return chunkA4SVOGG6_cjs.adminLogsRoutes; }
4738
+ get: function () { return chunkN7TDLOUE_cjs.adminLogsRoutes; }
2888
4739
  });
2889
4740
  Object.defineProperty(exports, "adminMediaRoutes", {
2890
4741
  enumerable: true,
2891
- get: function () { return chunkA4SVOGG6_cjs.adminMediaRoutes; }
4742
+ get: function () { return chunkN7TDLOUE_cjs.adminMediaRoutes; }
2892
4743
  });
2893
4744
  Object.defineProperty(exports, "adminPluginRoutes", {
2894
4745
  enumerable: true,
2895
- get: function () { return chunkA4SVOGG6_cjs.adminPluginRoutes; }
4746
+ get: function () { return chunkN7TDLOUE_cjs.adminPluginRoutes; }
2896
4747
  });
2897
4748
  Object.defineProperty(exports, "adminSettingsRoutes", {
2898
4749
  enumerable: true,
2899
- get: function () { return chunkA4SVOGG6_cjs.adminSettingsRoutes; }
4750
+ get: function () { return chunkN7TDLOUE_cjs.adminSettingsRoutes; }
2900
4751
  });
2901
4752
  Object.defineProperty(exports, "adminTestimonialsRoutes", {
2902
4753
  enumerable: true,
2903
- get: function () { return chunkA4SVOGG6_cjs.admin_testimonials_default; }
4754
+ get: function () { return chunkN7TDLOUE_cjs.admin_testimonials_default; }
2904
4755
  });
2905
4756
  Object.defineProperty(exports, "adminUsersRoutes", {
2906
4757
  enumerable: true,
2907
- get: function () { return chunkA4SVOGG6_cjs.userRoutes; }
4758
+ get: function () { return chunkN7TDLOUE_cjs.userRoutes; }
2908
4759
  });
2909
4760
  Object.defineProperty(exports, "apiContentCrudRoutes", {
2910
4761
  enumerable: true,
2911
- get: function () { return chunkA4SVOGG6_cjs.api_content_crud_default; }
4762
+ get: function () { return chunkN7TDLOUE_cjs.api_content_crud_default; }
2912
4763
  });
2913
4764
  Object.defineProperty(exports, "apiMediaRoutes", {
2914
4765
  enumerable: true,
2915
- get: function () { return chunkA4SVOGG6_cjs.api_media_default; }
4766
+ get: function () { return chunkN7TDLOUE_cjs.api_media_default; }
2916
4767
  });
2917
4768
  Object.defineProperty(exports, "apiRoutes", {
2918
4769
  enumerable: true,
2919
- get: function () { return chunkA4SVOGG6_cjs.api_default; }
4770
+ get: function () { return chunkN7TDLOUE_cjs.api_default; }
2920
4771
  });
2921
4772
  Object.defineProperty(exports, "apiSystemRoutes", {
2922
4773
  enumerable: true,
2923
- get: function () { return chunkA4SVOGG6_cjs.api_system_default; }
4774
+ get: function () { return chunkN7TDLOUE_cjs.api_system_default; }
2924
4775
  });
2925
4776
  Object.defineProperty(exports, "authRoutes", {
2926
4777
  enumerable: true,
2927
- get: function () { return chunkA4SVOGG6_cjs.auth_default; }
4778
+ get: function () { return chunkN7TDLOUE_cjs.auth_default; }
2928
4779
  });
2929
4780
  Object.defineProperty(exports, "Logger", {
2930
4781
  enumerable: true,
@@ -3092,235 +4943,243 @@ Object.defineProperty(exports, "workflowHistory", {
3092
4943
  });
3093
4944
  Object.defineProperty(exports, "AuthManager", {
3094
4945
  enumerable: true,
3095
- get: function () { return chunk7I5INVNR_cjs.AuthManager; }
4946
+ get: function () { return chunkT3YIKW2A_cjs.AuthManager; }
3096
4947
  });
3097
4948
  Object.defineProperty(exports, "PermissionManager", {
3098
4949
  enumerable: true,
3099
- get: function () { return chunk7I5INVNR_cjs.PermissionManager; }
4950
+ get: function () { return chunkT3YIKW2A_cjs.PermissionManager; }
3100
4951
  });
3101
4952
  Object.defineProperty(exports, "bootstrapMiddleware", {
3102
4953
  enumerable: true,
3103
- get: function () { return chunk7I5INVNR_cjs.bootstrapMiddleware; }
4954
+ get: function () { return chunkT3YIKW2A_cjs.bootstrapMiddleware; }
3104
4955
  });
3105
4956
  Object.defineProperty(exports, "cacheHeaders", {
3106
4957
  enumerable: true,
3107
- get: function () { return chunk7I5INVNR_cjs.cacheHeaders; }
4958
+ get: function () { return chunkT3YIKW2A_cjs.cacheHeaders; }
3108
4959
  });
3109
4960
  Object.defineProperty(exports, "compressionMiddleware", {
3110
4961
  enumerable: true,
3111
- get: function () { return chunk7I5INVNR_cjs.compressionMiddleware; }
4962
+ get: function () { return chunkT3YIKW2A_cjs.compressionMiddleware; }
3112
4963
  });
3113
4964
  Object.defineProperty(exports, "detailedLoggingMiddleware", {
3114
4965
  enumerable: true,
3115
- get: function () { return chunk7I5INVNR_cjs.detailedLoggingMiddleware; }
4966
+ get: function () { return chunkT3YIKW2A_cjs.detailedLoggingMiddleware; }
3116
4967
  });
3117
4968
  Object.defineProperty(exports, "getActivePlugins", {
3118
4969
  enumerable: true,
3119
- get: function () { return chunk7I5INVNR_cjs.getActivePlugins; }
4970
+ get: function () { return chunkT3YIKW2A_cjs.getActivePlugins; }
3120
4971
  });
3121
4972
  Object.defineProperty(exports, "isPluginActive", {
3122
4973
  enumerable: true,
3123
- get: function () { return chunk7I5INVNR_cjs.isPluginActive; }
4974
+ get: function () { return chunkT3YIKW2A_cjs.isPluginActive; }
3124
4975
  });
3125
4976
  Object.defineProperty(exports, "logActivity", {
3126
4977
  enumerable: true,
3127
- get: function () { return chunk7I5INVNR_cjs.logActivity; }
4978
+ get: function () { return chunkT3YIKW2A_cjs.logActivity; }
3128
4979
  });
3129
4980
  Object.defineProperty(exports, "loggingMiddleware", {
3130
4981
  enumerable: true,
3131
- get: function () { return chunk7I5INVNR_cjs.loggingMiddleware; }
4982
+ get: function () { return chunkT3YIKW2A_cjs.loggingMiddleware; }
3132
4983
  });
3133
4984
  Object.defineProperty(exports, "optionalAuth", {
3134
4985
  enumerable: true,
3135
- get: function () { return chunk7I5INVNR_cjs.optionalAuth; }
4986
+ get: function () { return chunkT3YIKW2A_cjs.optionalAuth; }
3136
4987
  });
3137
4988
  Object.defineProperty(exports, "performanceLoggingMiddleware", {
3138
4989
  enumerable: true,
3139
- get: function () { return chunk7I5INVNR_cjs.performanceLoggingMiddleware; }
4990
+ get: function () { return chunkT3YIKW2A_cjs.performanceLoggingMiddleware; }
3140
4991
  });
3141
4992
  Object.defineProperty(exports, "requireActivePlugin", {
3142
4993
  enumerable: true,
3143
- get: function () { return chunk7I5INVNR_cjs.requireActivePlugin; }
4994
+ get: function () { return chunkT3YIKW2A_cjs.requireActivePlugin; }
3144
4995
  });
3145
4996
  Object.defineProperty(exports, "requireActivePlugins", {
3146
4997
  enumerable: true,
3147
- get: function () { return chunk7I5INVNR_cjs.requireActivePlugins; }
4998
+ get: function () { return chunkT3YIKW2A_cjs.requireActivePlugins; }
3148
4999
  });
3149
5000
  Object.defineProperty(exports, "requireAnyPermission", {
3150
5001
  enumerable: true,
3151
- get: function () { return chunk7I5INVNR_cjs.requireAnyPermission; }
5002
+ get: function () { return chunkT3YIKW2A_cjs.requireAnyPermission; }
3152
5003
  });
3153
5004
  Object.defineProperty(exports, "requireAuth", {
3154
5005
  enumerable: true,
3155
- get: function () { return chunk7I5INVNR_cjs.requireAuth; }
5006
+ get: function () { return chunkT3YIKW2A_cjs.requireAuth; }
3156
5007
  });
3157
5008
  Object.defineProperty(exports, "requirePermission", {
3158
5009
  enumerable: true,
3159
- get: function () { return chunk7I5INVNR_cjs.requirePermission; }
5010
+ get: function () { return chunkT3YIKW2A_cjs.requirePermission; }
3160
5011
  });
3161
5012
  Object.defineProperty(exports, "requireRole", {
3162
5013
  enumerable: true,
3163
- get: function () { return chunk7I5INVNR_cjs.requireRole; }
5014
+ get: function () { return chunkT3YIKW2A_cjs.requireRole; }
3164
5015
  });
3165
5016
  Object.defineProperty(exports, "securityHeaders", {
3166
5017
  enumerable: true,
3167
- get: function () { return chunk7I5INVNR_cjs.securityHeaders; }
5018
+ get: function () { return chunkT3YIKW2A_cjs.securityHeaders; }
3168
5019
  });
3169
5020
  Object.defineProperty(exports, "securityLoggingMiddleware", {
3170
5021
  enumerable: true,
3171
- get: function () { return chunk7I5INVNR_cjs.securityLoggingMiddleware; }
5022
+ get: function () { return chunkT3YIKW2A_cjs.securityLoggingMiddleware; }
3172
5023
  });
3173
5024
  Object.defineProperty(exports, "PluginBootstrapService", {
3174
5025
  enumerable: true,
3175
- get: function () { return chunkILZ3DP4I_cjs.PluginBootstrapService; }
5026
+ get: function () { return chunkMPT5PA6U_cjs.PluginBootstrapService; }
3176
5027
  });
3177
5028
  Object.defineProperty(exports, "PluginServiceClass", {
3178
5029
  enumerable: true,
3179
- get: function () { return chunkILZ3DP4I_cjs.PluginService; }
5030
+ get: function () { return chunkMPT5PA6U_cjs.PluginService; }
3180
5031
  });
3181
5032
  Object.defineProperty(exports, "cleanupRemovedCollections", {
3182
5033
  enumerable: true,
3183
- get: function () { return chunkILZ3DP4I_cjs.cleanupRemovedCollections; }
5034
+ get: function () { return chunkMPT5PA6U_cjs.cleanupRemovedCollections; }
3184
5035
  });
3185
5036
  Object.defineProperty(exports, "fullCollectionSync", {
3186
5037
  enumerable: true,
3187
- get: function () { return chunkILZ3DP4I_cjs.fullCollectionSync; }
5038
+ get: function () { return chunkMPT5PA6U_cjs.fullCollectionSync; }
3188
5039
  });
3189
5040
  Object.defineProperty(exports, "getAvailableCollectionNames", {
3190
5041
  enumerable: true,
3191
- get: function () { return chunkILZ3DP4I_cjs.getAvailableCollectionNames; }
5042
+ get: function () { return chunkMPT5PA6U_cjs.getAvailableCollectionNames; }
3192
5043
  });
3193
5044
  Object.defineProperty(exports, "getManagedCollections", {
3194
5045
  enumerable: true,
3195
- get: function () { return chunkILZ3DP4I_cjs.getManagedCollections; }
5046
+ get: function () { return chunkMPT5PA6U_cjs.getManagedCollections; }
3196
5047
  });
3197
5048
  Object.defineProperty(exports, "isCollectionManaged", {
3198
5049
  enumerable: true,
3199
- get: function () { return chunkILZ3DP4I_cjs.isCollectionManaged; }
5050
+ get: function () { return chunkMPT5PA6U_cjs.isCollectionManaged; }
3200
5051
  });
3201
5052
  Object.defineProperty(exports, "loadCollectionConfig", {
3202
5053
  enumerable: true,
3203
- get: function () { return chunkILZ3DP4I_cjs.loadCollectionConfig; }
5054
+ get: function () { return chunkMPT5PA6U_cjs.loadCollectionConfig; }
3204
5055
  });
3205
5056
  Object.defineProperty(exports, "loadCollectionConfigs", {
3206
5057
  enumerable: true,
3207
- get: function () { return chunkILZ3DP4I_cjs.loadCollectionConfigs; }
5058
+ get: function () { return chunkMPT5PA6U_cjs.loadCollectionConfigs; }
3208
5059
  });
3209
5060
  Object.defineProperty(exports, "registerCollections", {
3210
5061
  enumerable: true,
3211
- get: function () { return chunkILZ3DP4I_cjs.registerCollections; }
5062
+ get: function () { return chunkMPT5PA6U_cjs.registerCollections; }
3212
5063
  });
3213
5064
  Object.defineProperty(exports, "syncCollection", {
3214
5065
  enumerable: true,
3215
- get: function () { return chunkILZ3DP4I_cjs.syncCollection; }
5066
+ get: function () { return chunkMPT5PA6U_cjs.syncCollection; }
3216
5067
  });
3217
5068
  Object.defineProperty(exports, "syncCollections", {
3218
5069
  enumerable: true,
3219
- get: function () { return chunkILZ3DP4I_cjs.syncCollections; }
5070
+ get: function () { return chunkMPT5PA6U_cjs.syncCollections; }
3220
5071
  });
3221
5072
  Object.defineProperty(exports, "validateCollectionConfig", {
3222
5073
  enumerable: true,
3223
- get: function () { return chunkILZ3DP4I_cjs.validateCollectionConfig; }
5074
+ get: function () { return chunkMPT5PA6U_cjs.validateCollectionConfig; }
3224
5075
  });
3225
5076
  Object.defineProperty(exports, "MigrationService", {
3226
5077
  enumerable: true,
3227
- get: function () { return chunk2MI3LZFH_cjs.MigrationService; }
5078
+ get: function () { return chunkIIRVZSP2_cjs.MigrationService; }
3228
5079
  });
3229
5080
  Object.defineProperty(exports, "renderFilterBar", {
3230
5081
  enumerable: true,
3231
- get: function () { return chunkYIXSSJWD_cjs.renderFilterBar; }
5082
+ get: function () { return chunk63K7XXRX_cjs.renderFilterBar; }
3232
5083
  });
3233
5084
  Object.defineProperty(exports, "getConfirmationDialogScript", {
3234
5085
  enumerable: true,
3235
- get: function () { return chunkAZLU3ROK_cjs.getConfirmationDialogScript; }
5086
+ get: function () { return chunkBZC4FYW7_cjs.getConfirmationDialogScript; }
3236
5087
  });
3237
5088
  Object.defineProperty(exports, "renderAlert", {
3238
5089
  enumerable: true,
3239
- get: function () { return chunkAZLU3ROK_cjs.renderAlert; }
5090
+ get: function () { return chunkBZC4FYW7_cjs.renderAlert; }
3240
5091
  });
3241
5092
  Object.defineProperty(exports, "renderConfirmationDialog", {
3242
5093
  enumerable: true,
3243
- get: function () { return chunkAZLU3ROK_cjs.renderConfirmationDialog; }
5094
+ get: function () { return chunkBZC4FYW7_cjs.renderConfirmationDialog; }
3244
5095
  });
3245
5096
  Object.defineProperty(exports, "renderForm", {
3246
5097
  enumerable: true,
3247
- get: function () { return chunkAZLU3ROK_cjs.renderForm; }
5098
+ get: function () { return chunkBZC4FYW7_cjs.renderForm; }
3248
5099
  });
3249
5100
  Object.defineProperty(exports, "renderFormField", {
3250
5101
  enumerable: true,
3251
- get: function () { return chunkAZLU3ROK_cjs.renderFormField; }
5102
+ get: function () { return chunkBZC4FYW7_cjs.renderFormField; }
3252
5103
  });
3253
5104
  Object.defineProperty(exports, "renderPagination", {
3254
5105
  enumerable: true,
3255
- get: function () { return chunkAZLU3ROK_cjs.renderPagination; }
5106
+ get: function () { return chunkBZC4FYW7_cjs.renderPagination; }
3256
5107
  });
3257
5108
  Object.defineProperty(exports, "renderTable", {
3258
5109
  enumerable: true,
3259
- get: function () { return chunkAZLU3ROK_cjs.renderTable; }
5110
+ get: function () { return chunkBZC4FYW7_cjs.renderTable; }
3260
5111
  });
3261
5112
  Object.defineProperty(exports, "HookSystemImpl", {
3262
5113
  enumerable: true,
3263
- get: function () { return chunkDTLB6UIH_cjs.HookSystemImpl; }
5114
+ get: function () { return chunkY72M3MVX_cjs.HookSystemImpl; }
3264
5115
  });
3265
5116
  Object.defineProperty(exports, "HookUtils", {
3266
5117
  enumerable: true,
3267
- get: function () { return chunkDTLB6UIH_cjs.HookUtils; }
5118
+ get: function () { return chunkY72M3MVX_cjs.HookUtils; }
3268
5119
  });
3269
5120
  Object.defineProperty(exports, "PluginManagerClass", {
3270
5121
  enumerable: true,
3271
- get: function () { return chunkDTLB6UIH_cjs.PluginManager; }
5122
+ get: function () { return chunkY72M3MVX_cjs.PluginManager; }
3272
5123
  });
3273
5124
  Object.defineProperty(exports, "PluginRegistryImpl", {
3274
5125
  enumerable: true,
3275
- get: function () { return chunkDTLB6UIH_cjs.PluginRegistryImpl; }
5126
+ get: function () { return chunkY72M3MVX_cjs.PluginRegistryImpl; }
3276
5127
  });
3277
5128
  Object.defineProperty(exports, "PluginValidatorClass", {
3278
5129
  enumerable: true,
3279
- get: function () { return chunkDTLB6UIH_cjs.PluginValidator; }
5130
+ get: function () { return chunkY72M3MVX_cjs.PluginValidator; }
3280
5131
  });
3281
5132
  Object.defineProperty(exports, "ScopedHookSystemClass", {
3282
5133
  enumerable: true,
3283
- get: function () { return chunkDTLB6UIH_cjs.ScopedHookSystem; }
5134
+ get: function () { return chunkY72M3MVX_cjs.ScopedHookSystem; }
5135
+ });
5136
+ Object.defineProperty(exports, "PluginBuilder", {
5137
+ enumerable: true,
5138
+ get: function () { return chunkYHW27CBV_cjs.PluginBuilder; }
5139
+ });
5140
+ Object.defineProperty(exports, "PluginHelpers", {
5141
+ enumerable: true,
5142
+ get: function () { return chunkYHW27CBV_cjs.PluginHelpers; }
3284
5143
  });
3285
5144
  Object.defineProperty(exports, "QueryFilterBuilder", {
3286
5145
  enumerable: true,
3287
- get: function () { return chunkFYEDK7K7_cjs.QueryFilterBuilder; }
5146
+ get: function () { return chunkYMTTGHEK_cjs.QueryFilterBuilder; }
3288
5147
  });
3289
5148
  Object.defineProperty(exports, "SONICJS_VERSION", {
3290
5149
  enumerable: true,
3291
- get: function () { return chunkFYEDK7K7_cjs.SONICJS_VERSION; }
5150
+ get: function () { return chunkYMTTGHEK_cjs.SONICJS_VERSION; }
3292
5151
  });
3293
5152
  Object.defineProperty(exports, "TemplateRenderer", {
3294
5153
  enumerable: true,
3295
- get: function () { return chunkFYEDK7K7_cjs.TemplateRenderer; }
5154
+ get: function () { return chunkYMTTGHEK_cjs.TemplateRenderer; }
3296
5155
  });
3297
5156
  Object.defineProperty(exports, "buildQuery", {
3298
5157
  enumerable: true,
3299
- get: function () { return chunkFYEDK7K7_cjs.buildQuery; }
5158
+ get: function () { return chunkYMTTGHEK_cjs.buildQuery; }
3300
5159
  });
3301
5160
  Object.defineProperty(exports, "escapeHtml", {
3302
5161
  enumerable: true,
3303
- get: function () { return chunkFYEDK7K7_cjs.escapeHtml; }
5162
+ get: function () { return chunkYMTTGHEK_cjs.escapeHtml; }
3304
5163
  });
3305
5164
  Object.defineProperty(exports, "getCoreVersion", {
3306
5165
  enumerable: true,
3307
- get: function () { return chunkFYEDK7K7_cjs.getCoreVersion; }
5166
+ get: function () { return chunkYMTTGHEK_cjs.getCoreVersion; }
3308
5167
  });
3309
5168
  Object.defineProperty(exports, "renderTemplate", {
3310
5169
  enumerable: true,
3311
- get: function () { return chunkFYEDK7K7_cjs.renderTemplate; }
5170
+ get: function () { return chunkYMTTGHEK_cjs.renderTemplate; }
3312
5171
  });
3313
5172
  Object.defineProperty(exports, "sanitizeInput", {
3314
5173
  enumerable: true,
3315
- get: function () { return chunkFYEDK7K7_cjs.sanitizeInput; }
5174
+ get: function () { return chunkYMTTGHEK_cjs.sanitizeInput; }
3316
5175
  });
3317
5176
  Object.defineProperty(exports, "sanitizeObject", {
3318
5177
  enumerable: true,
3319
- get: function () { return chunkFYEDK7K7_cjs.sanitizeObject; }
5178
+ get: function () { return chunkYMTTGHEK_cjs.sanitizeObject; }
3320
5179
  });
3321
5180
  Object.defineProperty(exports, "templateRenderer", {
3322
5181
  enumerable: true,
3323
- get: function () { return chunkFYEDK7K7_cjs.templateRenderer; }
5182
+ get: function () { return chunkYMTTGHEK_cjs.templateRenderer; }
3324
5183
  });
3325
5184
  Object.defineProperty(exports, "metricsTracker", {
3326
5185
  enumerable: true,