@retrivora-ai/rag-engine 0.1.6 → 0.1.7

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.
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  QdrantProvider
3
- } from "./chunk-5K23H7JL.mjs";
3
+ } from "./chunk-CWQQHAF6.mjs";
4
4
  import "./chunk-VOIWNO5O.mjs";
5
5
  import "./chunk-I4E63NIC.mjs";
6
6
  export {
@@ -67,6 +67,7 @@ function getRagConfig(env = process.env) {
67
67
  } else if (vectorProvider === "qdrant") {
68
68
  vectorDbOptions.baseUrl = (_m = readString(env, "QDRANT_URL")) != null ? _m : "http://localhost:6333";
69
69
  vectorDbOptions.apiKey = readString(env, "QDRANT_API_KEY");
70
+ vectorDbOptions.dimensions = embeddingDimensions;
70
71
  }
71
72
  const llmApiKeyByProvider = {
72
73
  openai: readString(env, "OPENAI_API_KEY"),
@@ -603,9 +604,11 @@ var ConfigValidator = class {
603
604
  return errors;
604
605
  }
605
606
  static isValidCSSColor(color) {
606
- const s = new Option().style;
607
- s.color = color;
608
- return s.color !== "";
607
+ const hexRegex = /^#([0-9a-f]{3}){1,2}$/i;
608
+ const rgbRegex = /^rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*(\d+(?:\.\d+)?))?\)$/i;
609
+ const hslRegex = /^hsla?\((\d+),\s*(\d+)%,\s*(\d+)%(?:,\s*(\d+(?:\.\d+)?))?\)$/i;
610
+ const namedColors = ["red", "blue", "green", "black", "white", "transparent"];
611
+ return hexRegex.test(color) || rgbRegex.test(color) || hslRegex.test(color) || namedColors.includes(color.toLowerCase());
609
612
  }
610
613
  /**
611
614
  * Throws if there are error-level validation issues.
@@ -720,7 +723,7 @@ var ProviderRegistry = class {
720
723
  const { MilvusProvider } = await import("./MilvusProvider-OO6QGZDZ.mjs");
721
724
  return new MilvusProvider(config);
722
725
  case "qdrant":
723
- const { QdrantProvider } = await import("./QdrantProvider-NYGHAA7C.mjs");
726
+ const { QdrantProvider } = await import("./QdrantProvider-VAED5VA7.mjs");
724
727
  return new QdrantProvider(config);
725
728
  case "chromadb":
726
729
  const { ChromaDBProvider } = await import("./ChromaDBProvider-QNI7UCX4.mjs");
@@ -1261,7 +1264,10 @@ var ProviderHealthCheck = class {
1261
1264
  const opts = config.options;
1262
1265
  const baseUrl = opts.baseUrl || opts.url || "http://localhost:6333";
1263
1266
  try {
1264
- const response = await fetch(`${baseUrl}/health`);
1267
+ const apiKey = opts.apiKey;
1268
+ const response = await fetch(`${baseUrl}/`, {
1269
+ headers: apiKey ? { "api-key": apiKey } : {}
1270
+ });
1265
1271
  if (!response.ok) {
1266
1272
  throw new Error(`Health check returned ${response.status}`);
1267
1273
  }
@@ -7,6 +7,7 @@ import {
7
7
 
8
8
  // src/providers/vectordb/QdrantProvider.ts
9
9
  import axios from "axios";
10
+ import crypto from "crypto";
10
11
  var QdrantProvider = class extends BaseVectorProvider {
11
12
  constructor(config) {
12
13
  super(config);
@@ -22,8 +23,36 @@ var QdrantProvider = class extends BaseVectorProvider {
22
23
  }
23
24
  async initialize() {
24
25
  await this.ping();
26
+ await this.ensureCollection();
25
27
  await this.ensureIndex();
26
28
  }
29
+ /**
30
+ * Ensures the collection exists. Creates it if missing.
31
+ */
32
+ async ensureCollection() {
33
+ var _a;
34
+ try {
35
+ const opts = this.config.options;
36
+ const dimensions = opts.dimensions || 1536;
37
+ await this.http.get(`/collections/${this.indexName}`);
38
+ console.log(`[QdrantProvider] \u2705 Collection "${this.indexName}" already exists.`);
39
+ } catch (err) {
40
+ if (axios.isAxiosError(err) && ((_a = err.response) == null ? void 0 : _a.status) === 404) {
41
+ const opts = this.config.options;
42
+ const dimensions = opts.dimensions || 1536;
43
+ console.log(`[QdrantProvider] \u23F3 Creating collection "${this.indexName}" (dimensions: ${dimensions})...`);
44
+ await this.http.put(`/collections/${this.indexName}`, {
45
+ vectors: {
46
+ size: dimensions,
47
+ distance: "Cosine"
48
+ }
49
+ });
50
+ console.log(`[QdrantProvider] \u2705 Created collection "${this.indexName}"`);
51
+ } else {
52
+ throw err;
53
+ }
54
+ }
55
+ }
27
56
  /**
28
57
  * Ensures that the 'namespace' field has a keyword index for efficient filtering.
29
58
  * Qdrant requires this for search filters to work in many configurations.
@@ -56,7 +85,7 @@ var QdrantProvider = class extends BaseVectorProvider {
56
85
  async batchUpsert(docs, namespace) {
57
86
  const payload = {
58
87
  points: docs.map((doc) => ({
59
- id: doc.id,
88
+ id: this.normalizeId(doc.id),
60
89
  vector: doc.vector,
61
90
  payload: __spreadValues({
62
91
  content: doc.content,
@@ -90,7 +119,7 @@ var QdrantProvider = class extends BaseVectorProvider {
90
119
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
91
120
  async delete(id, _namespace) {
92
121
  await this.http.post(`/collections/${this.indexName}/points/delete`, {
93
- points: [id]
122
+ points: [this.normalizeId(id)]
94
123
  });
95
124
  }
96
125
  async deleteNamespace(_namespace) {
@@ -108,6 +137,17 @@ var QdrantProvider = class extends BaseVectorProvider {
108
137
  return false;
109
138
  }
110
139
  }
140
+ /**
141
+ * Normalizes an ID into a format Qdrant accepts (UUID or integer).
142
+ * For string IDs that aren't UUIDs, it generates a deterministic UUID v5-like hash.
143
+ */
144
+ normalizeId(id) {
145
+ if (typeof id === "number") return id;
146
+ const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
147
+ if (uuidRegex.test(id)) return id.toLowerCase();
148
+ const hash = crypto.createHash("md5").update(id).digest("hex");
149
+ return `${hash.slice(0, 8)}-${hash.slice(8, 12)}-${hash.slice(12, 16)}-${hash.slice(16, 20)}-${hash.slice(20, 32)}`;
150
+ }
111
151
  async disconnect() {
112
152
  }
113
153
  };
@@ -955,11 +955,12 @@ var QdrantProvider_exports = {};
955
955
  __export(QdrantProvider_exports, {
956
956
  QdrantProvider: () => QdrantProvider
957
957
  });
958
- var import_axios4, QdrantProvider;
958
+ var import_axios4, import_crypto, QdrantProvider;
959
959
  var init_QdrantProvider = __esm({
960
960
  "src/providers/vectordb/QdrantProvider.ts"() {
961
961
  "use strict";
962
962
  import_axios4 = __toESM(require("axios"));
963
+ import_crypto = __toESM(require("crypto"));
963
964
  init_BaseVectorProvider();
964
965
  QdrantProvider = class extends BaseVectorProvider {
965
966
  constructor(config) {
@@ -976,8 +977,36 @@ var init_QdrantProvider = __esm({
976
977
  }
977
978
  async initialize() {
978
979
  await this.ping();
980
+ await this.ensureCollection();
979
981
  await this.ensureIndex();
980
982
  }
983
+ /**
984
+ * Ensures the collection exists. Creates it if missing.
985
+ */
986
+ async ensureCollection() {
987
+ var _a;
988
+ try {
989
+ const opts = this.config.options;
990
+ const dimensions = opts.dimensions || 1536;
991
+ await this.http.get(`/collections/${this.indexName}`);
992
+ console.log(`[QdrantProvider] \u2705 Collection "${this.indexName}" already exists.`);
993
+ } catch (err) {
994
+ if (import_axios4.default.isAxiosError(err) && ((_a = err.response) == null ? void 0 : _a.status) === 404) {
995
+ const opts = this.config.options;
996
+ const dimensions = opts.dimensions || 1536;
997
+ console.log(`[QdrantProvider] \u23F3 Creating collection "${this.indexName}" (dimensions: ${dimensions})...`);
998
+ await this.http.put(`/collections/${this.indexName}`, {
999
+ vectors: {
1000
+ size: dimensions,
1001
+ distance: "Cosine"
1002
+ }
1003
+ });
1004
+ console.log(`[QdrantProvider] \u2705 Created collection "${this.indexName}"`);
1005
+ } else {
1006
+ throw err;
1007
+ }
1008
+ }
1009
+ }
981
1010
  /**
982
1011
  * Ensures that the 'namespace' field has a keyword index for efficient filtering.
983
1012
  * Qdrant requires this for search filters to work in many configurations.
@@ -1010,7 +1039,7 @@ var init_QdrantProvider = __esm({
1010
1039
  async batchUpsert(docs, namespace) {
1011
1040
  const payload = {
1012
1041
  points: docs.map((doc) => ({
1013
- id: doc.id,
1042
+ id: this.normalizeId(doc.id),
1014
1043
  vector: doc.vector,
1015
1044
  payload: __spreadValues({
1016
1045
  content: doc.content,
@@ -1044,7 +1073,7 @@ var init_QdrantProvider = __esm({
1044
1073
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
1045
1074
  async delete(id, _namespace) {
1046
1075
  await this.http.post(`/collections/${this.indexName}/points/delete`, {
1047
- points: [id]
1076
+ points: [this.normalizeId(id)]
1048
1077
  });
1049
1078
  }
1050
1079
  async deleteNamespace(_namespace) {
@@ -1062,6 +1091,17 @@ var init_QdrantProvider = __esm({
1062
1091
  return false;
1063
1092
  }
1064
1093
  }
1094
+ /**
1095
+ * Normalizes an ID into a format Qdrant accepts (UUID or integer).
1096
+ * For string IDs that aren't UUIDs, it generates a deterministic UUID v5-like hash.
1097
+ */
1098
+ normalizeId(id) {
1099
+ if (typeof id === "number") return id;
1100
+ const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
1101
+ if (uuidRegex.test(id)) return id.toLowerCase();
1102
+ const hash = import_crypto.default.createHash("md5").update(id).digest("hex");
1103
+ return `${hash.slice(0, 8)}-${hash.slice(8, 12)}-${hash.slice(12, 16)}-${hash.slice(16, 20)}-${hash.slice(20, 32)}`;
1104
+ }
1065
1105
  async disconnect() {
1066
1106
  }
1067
1107
  };
@@ -1418,6 +1458,7 @@ function getRagConfig(env = process.env) {
1418
1458
  } else if (vectorProvider === "qdrant") {
1419
1459
  vectorDbOptions.baseUrl = (_m = readString(env, "QDRANT_URL")) != null ? _m : "http://localhost:6333";
1420
1460
  vectorDbOptions.apiKey = readString(env, "QDRANT_API_KEY");
1461
+ vectorDbOptions.dimensions = embeddingDimensions;
1421
1462
  }
1422
1463
  const llmApiKeyByProvider = {
1423
1464
  openai: readString(env, "OPENAI_API_KEY"),
@@ -1954,9 +1995,11 @@ var ConfigValidator = class {
1954
1995
  return errors;
1955
1996
  }
1956
1997
  static isValidCSSColor(color) {
1957
- const s = new Option().style;
1958
- s.color = color;
1959
- return s.color !== "";
1998
+ const hexRegex = /^#([0-9a-f]{3}){1,2}$/i;
1999
+ const rgbRegex = /^rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*(\d+(?:\.\d+)?))?\)$/i;
2000
+ const hslRegex = /^hsla?\((\d+),\s*(\d+)%,\s*(\d+)%(?:,\s*(\d+(?:\.\d+)?))?\)$/i;
2001
+ const namedColors = ["red", "blue", "green", "black", "white", "transparent"];
2002
+ return hexRegex.test(color) || rgbRegex.test(color) || hslRegex.test(color) || namedColors.includes(color.toLowerCase());
1960
2003
  }
1961
2004
  /**
1962
2005
  * Throws if there are error-level validation issues.
@@ -2613,7 +2656,10 @@ var ProviderHealthCheck = class {
2613
2656
  const opts = config.options;
2614
2657
  const baseUrl = opts.baseUrl || opts.url || "http://localhost:6333";
2615
2658
  try {
2616
- const response = await fetch(`${baseUrl}/health`);
2659
+ const apiKey = opts.apiKey;
2660
+ const response = await fetch(`${baseUrl}/`, {
2661
+ headers: apiKey ? { "api-key": apiKey } : {}
2662
+ });
2617
2663
  if (!response.ok) {
2618
2664
  throw new Error(`Health check returned ${response.status}`);
2619
2665
  }
@@ -3,7 +3,7 @@ import {
3
3
  createHealthHandler,
4
4
  createIngestHandler,
5
5
  createUploadHandler
6
- } from "../chunk-HSBXE2WV.mjs";
6
+ } from "../chunk-7YQWGERZ.mjs";
7
7
  import "../chunk-JI6VD5TJ.mjs";
8
8
  import "../chunk-UKDXCXW7.mjs";
9
9
  import "../chunk-I4E63NIC.mjs";
package/dist/server.d.mts CHANGED
@@ -266,6 +266,10 @@ declare class QdrantProvider extends BaseVectorProvider {
266
266
  private http;
267
267
  constructor(config: VectorDBConfig);
268
268
  initialize(): Promise<void>;
269
+ /**
270
+ * Ensures the collection exists. Creates it if missing.
271
+ */
272
+ private ensureCollection;
269
273
  /**
270
274
  * Ensures that the 'namespace' field has a keyword index for efficient filtering.
271
275
  * Qdrant requires this for search filters to work in many configurations.
@@ -277,6 +281,11 @@ declare class QdrantProvider extends BaseVectorProvider {
277
281
  delete(id: string | number, _namespace?: string): Promise<void>;
278
282
  deleteNamespace(_namespace: string): Promise<void>;
279
283
  ping(): Promise<boolean>;
284
+ /**
285
+ * Normalizes an ID into a format Qdrant accepts (UUID or integer).
286
+ * For string IDs that aren't UUIDs, it generates a deterministic UUID v5-like hash.
287
+ */
288
+ private normalizeId;
280
289
  disconnect(): Promise<void>;
281
290
  }
282
291
 
package/dist/server.d.ts CHANGED
@@ -266,6 +266,10 @@ declare class QdrantProvider extends BaseVectorProvider {
266
266
  private http;
267
267
  constructor(config: VectorDBConfig);
268
268
  initialize(): Promise<void>;
269
+ /**
270
+ * Ensures the collection exists. Creates it if missing.
271
+ */
272
+ private ensureCollection;
269
273
  /**
270
274
  * Ensures that the 'namespace' field has a keyword index for efficient filtering.
271
275
  * Qdrant requires this for search filters to work in many configurations.
@@ -277,6 +281,11 @@ declare class QdrantProvider extends BaseVectorProvider {
277
281
  delete(id: string | number, _namespace?: string): Promise<void>;
278
282
  deleteNamespace(_namespace: string): Promise<void>;
279
283
  ping(): Promise<boolean>;
284
+ /**
285
+ * Normalizes an ID into a format Qdrant accepts (UUID or integer).
286
+ * For string IDs that aren't UUIDs, it generates a deterministic UUID v5-like hash.
287
+ */
288
+ private normalizeId;
280
289
  disconnect(): Promise<void>;
281
290
  }
282
291
 
package/dist/server.js CHANGED
@@ -955,11 +955,12 @@ var QdrantProvider_exports = {};
955
955
  __export(QdrantProvider_exports, {
956
956
  QdrantProvider: () => QdrantProvider
957
957
  });
958
- var import_axios4, QdrantProvider;
958
+ var import_axios4, import_crypto, QdrantProvider;
959
959
  var init_QdrantProvider = __esm({
960
960
  "src/providers/vectordb/QdrantProvider.ts"() {
961
961
  "use strict";
962
962
  import_axios4 = __toESM(require("axios"));
963
+ import_crypto = __toESM(require("crypto"));
963
964
  init_BaseVectorProvider();
964
965
  QdrantProvider = class extends BaseVectorProvider {
965
966
  constructor(config) {
@@ -976,8 +977,36 @@ var init_QdrantProvider = __esm({
976
977
  }
977
978
  async initialize() {
978
979
  await this.ping();
980
+ await this.ensureCollection();
979
981
  await this.ensureIndex();
980
982
  }
983
+ /**
984
+ * Ensures the collection exists. Creates it if missing.
985
+ */
986
+ async ensureCollection() {
987
+ var _a;
988
+ try {
989
+ const opts = this.config.options;
990
+ const dimensions = opts.dimensions || 1536;
991
+ await this.http.get(`/collections/${this.indexName}`);
992
+ console.log(`[QdrantProvider] \u2705 Collection "${this.indexName}" already exists.`);
993
+ } catch (err) {
994
+ if (import_axios4.default.isAxiosError(err) && ((_a = err.response) == null ? void 0 : _a.status) === 404) {
995
+ const opts = this.config.options;
996
+ const dimensions = opts.dimensions || 1536;
997
+ console.log(`[QdrantProvider] \u23F3 Creating collection "${this.indexName}" (dimensions: ${dimensions})...`);
998
+ await this.http.put(`/collections/${this.indexName}`, {
999
+ vectors: {
1000
+ size: dimensions,
1001
+ distance: "Cosine"
1002
+ }
1003
+ });
1004
+ console.log(`[QdrantProvider] \u2705 Created collection "${this.indexName}"`);
1005
+ } else {
1006
+ throw err;
1007
+ }
1008
+ }
1009
+ }
981
1010
  /**
982
1011
  * Ensures that the 'namespace' field has a keyword index for efficient filtering.
983
1012
  * Qdrant requires this for search filters to work in many configurations.
@@ -1010,7 +1039,7 @@ var init_QdrantProvider = __esm({
1010
1039
  async batchUpsert(docs, namespace) {
1011
1040
  const payload = {
1012
1041
  points: docs.map((doc) => ({
1013
- id: doc.id,
1042
+ id: this.normalizeId(doc.id),
1014
1043
  vector: doc.vector,
1015
1044
  payload: __spreadValues({
1016
1045
  content: doc.content,
@@ -1044,7 +1073,7 @@ var init_QdrantProvider = __esm({
1044
1073
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
1045
1074
  async delete(id, _namespace) {
1046
1075
  await this.http.post(`/collections/${this.indexName}/points/delete`, {
1047
- points: [id]
1076
+ points: [this.normalizeId(id)]
1048
1077
  });
1049
1078
  }
1050
1079
  async deleteNamespace(_namespace) {
@@ -1062,6 +1091,17 @@ var init_QdrantProvider = __esm({
1062
1091
  return false;
1063
1092
  }
1064
1093
  }
1094
+ /**
1095
+ * Normalizes an ID into a format Qdrant accepts (UUID or integer).
1096
+ * For string IDs that aren't UUIDs, it generates a deterministic UUID v5-like hash.
1097
+ */
1098
+ normalizeId(id) {
1099
+ if (typeof id === "number") return id;
1100
+ const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
1101
+ if (uuidRegex.test(id)) return id.toLowerCase();
1102
+ const hash = import_crypto.default.createHash("md5").update(id).digest("hex");
1103
+ return `${hash.slice(0, 8)}-${hash.slice(8, 12)}-${hash.slice(12, 16)}-${hash.slice(16, 20)}-${hash.slice(20, 32)}`;
1104
+ }
1065
1105
  async disconnect() {
1066
1106
  }
1067
1107
  };
@@ -1436,6 +1476,7 @@ function getRagConfig(env = process.env) {
1436
1476
  } else if (vectorProvider === "qdrant") {
1437
1477
  vectorDbOptions.baseUrl = (_m = readString(env, "QDRANT_URL")) != null ? _m : "http://localhost:6333";
1438
1478
  vectorDbOptions.apiKey = readString(env, "QDRANT_API_KEY");
1479
+ vectorDbOptions.dimensions = embeddingDimensions;
1439
1480
  }
1440
1481
  const llmApiKeyByProvider = {
1441
1482
  openai: readString(env, "OPENAI_API_KEY"),
@@ -1972,9 +2013,11 @@ var ConfigValidator = class {
1972
2013
  return errors;
1973
2014
  }
1974
2015
  static isValidCSSColor(color) {
1975
- const s = new Option().style;
1976
- s.color = color;
1977
- return s.color !== "";
2016
+ const hexRegex = /^#([0-9a-f]{3}){1,2}$/i;
2017
+ const rgbRegex = /^rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*(\d+(?:\.\d+)?))?\)$/i;
2018
+ const hslRegex = /^hsla?\((\d+),\s*(\d+)%,\s*(\d+)%(?:,\s*(\d+(?:\.\d+)?))?\)$/i;
2019
+ const namedColors = ["red", "blue", "green", "black", "white", "transparent"];
2020
+ return hexRegex.test(color) || rgbRegex.test(color) || hslRegex.test(color) || namedColors.includes(color.toLowerCase());
1978
2021
  }
1979
2022
  /**
1980
2023
  * Throws if there are error-level validation issues.
@@ -2631,7 +2674,10 @@ var ProviderHealthCheck = class {
2631
2674
  const opts = config.options;
2632
2675
  const baseUrl = opts.baseUrl || opts.url || "http://localhost:6333";
2633
2676
  try {
2634
- const response = await fetch(`${baseUrl}/health`);
2677
+ const apiKey = opts.apiKey;
2678
+ const response = await fetch(`${baseUrl}/`, {
2679
+ headers: apiKey ? { "api-key": apiKey } : {}
2680
+ });
2635
2681
  if (!response.ok) {
2636
2682
  throw new Error(`Health check returned ${response.status}`);
2637
2683
  }
package/dist/server.mjs CHANGED
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  QdrantProvider
3
- } from "./chunk-5K23H7JL.mjs";
3
+ } from "./chunk-CWQQHAF6.mjs";
4
4
  import {
5
5
  ChromaDBProvider
6
6
  } from "./chunk-HUGLYKD6.mjs";
@@ -21,7 +21,7 @@ import {
21
21
  createIngestHandler,
22
22
  createUploadHandler,
23
23
  getRagConfig
24
- } from "./chunk-HSBXE2WV.mjs";
24
+ } from "./chunk-7YQWGERZ.mjs";
25
25
  import {
26
26
  AnthropicProvider,
27
27
  LLMFactory,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@retrivora-ai/rag-engine",
3
- "version": "0.1.6",
3
+ "version": "0.1.7",
4
4
  "description": "Retrivora AI is a plug-and-play AI engine for RAG chat experiences — generic vector DB + LLM provider, embeddable or standalone.",
5
5
  "author": "Abhinav Alkuchi",
6
6
  "license": "MIT",
@@ -76,6 +76,7 @@ export function getRagConfig(env: Record<string, string | undefined> = process.e
76
76
  } else if (vectorProvider === 'qdrant') {
77
77
  vectorDbOptions.baseUrl = readString(env, 'QDRANT_URL') ?? 'http://localhost:6333';
78
78
  vectorDbOptions.apiKey = readString(env, 'QDRANT_API_KEY');
79
+ vectorDbOptions.dimensions = embeddingDimensions;
79
80
  }
80
81
 
81
82
  const llmApiKeyByProvider: Record<string, string | undefined> = {
@@ -526,10 +526,18 @@ export class ConfigValidator {
526
526
  }
527
527
 
528
528
  private static isValidCSSColor(color: string): boolean {
529
- // Check if valid CSS color
530
- const s = new Option().style;
531
- s.color = color;
532
- return s.color !== '';
529
+ // Basic regex for hex, rgb, rgba, hsl, hsla
530
+ const hexRegex = /^#([0-9a-f]{3}){1,2}$/i;
531
+ const rgbRegex = /^rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*(\d+(?:\.\d+)?))?\)$/i;
532
+ const hslRegex = /^hsla?\((\d+),\s*(\d+)%,\s*(\d+)%(?:,\s*(\d+(?:\.\d+)?))?\)$/i;
533
+ const namedColors = ['red', 'blue', 'green', 'black', 'white', 'transparent']; // basic set
534
+
535
+ return (
536
+ hexRegex.test(color) ||
537
+ rgbRegex.test(color) ||
538
+ hslRegex.test(color) ||
539
+ namedColors.includes(color.toLowerCase())
540
+ );
533
541
  }
534
542
 
535
543
  /**
@@ -235,7 +235,10 @@ export class ProviderHealthCheck {
235
235
  const baseUrl = (opts.baseUrl as string) || (opts.url as string) || 'http://localhost:6333';
236
236
 
237
237
  try {
238
- const response = await fetch(`${baseUrl}/health`);
238
+ const apiKey = opts.apiKey as string | undefined;
239
+ const response = await fetch(`${baseUrl}/`, {
240
+ headers: apiKey ? { 'api-key': apiKey } : {},
241
+ });
239
242
 
240
243
  if (!response.ok) {
241
244
  throw new Error(`Health check returned ${response.status}`);
@@ -1,4 +1,5 @@
1
1
  import axios, { AxiosInstance } from 'axios';
2
+ import crypto from 'crypto';
2
3
  import { VectorDBConfig } from '../../config/RagConfig';
3
4
  import { BaseVectorProvider } from './BaseVectorProvider';
4
5
  import { VectorMatch, UpsertDocument } from '../../types';
@@ -26,9 +27,39 @@ export class QdrantProvider extends BaseVectorProvider {
26
27
 
27
28
  async initialize(): Promise<void> {
28
29
  await this.ping();
30
+ await this.ensureCollection();
29
31
  await this.ensureIndex();
30
32
  }
31
33
 
34
+ /**
35
+ * Ensures the collection exists. Creates it if missing.
36
+ */
37
+ private async ensureCollection(): Promise<void> {
38
+ try {
39
+ const opts = this.config.options as Record<string, unknown>;
40
+ const dimensions = (opts.dimensions as number) || 1536;
41
+
42
+ await this.http.get(`/collections/${this.indexName}`);
43
+ console.log(`[QdrantProvider] ✅ Collection "${this.indexName}" already exists.`);
44
+ } catch (err) {
45
+ if (axios.isAxiosError(err) && err.response?.status === 404) {
46
+ const opts = this.config.options as Record<string, unknown>;
47
+ const dimensions = (opts.dimensions as number) || 1536;
48
+
49
+ console.log(`[QdrantProvider] ⏳ Creating collection "${this.indexName}" (dimensions: ${dimensions})...`);
50
+ await this.http.put(`/collections/${this.indexName}`, {
51
+ vectors: {
52
+ size: dimensions,
53
+ distance: 'Cosine',
54
+ },
55
+ });
56
+ console.log(`[QdrantProvider] ✅ Created collection "${this.indexName}"`);
57
+ } else {
58
+ throw err;
59
+ }
60
+ }
61
+ }
62
+
32
63
  /**
33
64
  * Ensures that the 'namespace' field has a keyword index for efficient filtering.
34
65
  * Qdrant requires this for search filters to work in many configurations.
@@ -66,7 +97,7 @@ export class QdrantProvider extends BaseVectorProvider {
66
97
  async batchUpsert(docs: UpsertDocument[], namespace?: string): Promise<void> {
67
98
  const payload = {
68
99
  points: docs.map(doc => ({
69
- id: doc.id,
100
+ id: this.normalizeId(doc.id),
70
101
  vector: doc.vector,
71
102
  payload: {
72
103
  content: doc.content,
@@ -100,7 +131,7 @@ export class QdrantProvider extends BaseVectorProvider {
100
131
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
101
132
  async delete(id: string | number, _namespace?: string): Promise<void> {
102
133
  await this.http.post(`/collections/${this.indexName}/points/delete`, {
103
- points: [id],
134
+ points: [this.normalizeId(id)],
104
135
  });
105
136
  }
106
137
 
@@ -121,5 +152,21 @@ export class QdrantProvider extends BaseVectorProvider {
121
152
  }
122
153
  }
123
154
 
155
+ /**
156
+ * Normalizes an ID into a format Qdrant accepts (UUID or integer).
157
+ * For string IDs that aren't UUIDs, it generates a deterministic UUID v5-like hash.
158
+ */
159
+ private normalizeId(id: string | number): string | number {
160
+ if (typeof id === 'number') return id;
161
+
162
+ // Check if already a valid UUID
163
+ const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
164
+ if (uuidRegex.test(id)) return id.toLowerCase();
165
+
166
+ // Hash string to deterministic UUID
167
+ const hash = crypto.createHash('md5').update(id).digest('hex');
168
+ return `${hash.slice(0, 8)}-${hash.slice(8, 12)}-${hash.slice(12, 16)}-${hash.slice(16, 20)}-${hash.slice(20, 32)}`;
169
+ }
170
+
124
171
  async disconnect(): Promise<void> {}
125
172
  }