@retrivora-ai/rag-engine 1.0.7 → 1.0.9

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-3DSHW676.mjs";
3
+ } from "./chunk-NT5VP7MT.mjs";
4
4
  import "./chunk-IMP6FUCY.mjs";
5
5
  import "./chunk-X4TOT24V.mjs";
6
6
  export {
@@ -222,6 +222,8 @@ interface UIConfig {
222
222
  borderRadius?: 'none' | 'sm' | 'md' | 'lg' | 'xl' | 'full';
223
223
  /** Whether to allow file uploads directly from the UI */
224
224
  allowUpload?: boolean;
225
+ /** Whether to allow manual resizing of the chat window. Defaults to true. */
226
+ allowResize?: boolean;
225
227
  }
226
228
  interface RAGConfig {
227
229
  /** Number of top-K chunks retrieved per query */
@@ -222,6 +222,8 @@ interface UIConfig {
222
222
  borderRadius?: 'none' | 'sm' | 'md' | 'lg' | 'xl' | 'full';
223
223
  /** Whether to allow file uploads directly from the UI */
224
224
  allowUpload?: boolean;
225
+ /** Whether to allow manual resizing of the chat window. Defaults to true. */
226
+ allowResize?: boolean;
225
227
  }
226
228
  interface RAGConfig {
227
229
  /** Number of top-K chunks retrieved per query */
@@ -1106,7 +1106,7 @@ var ProviderRegistry = class {
1106
1106
  return MilvusProvider;
1107
1107
  }
1108
1108
  case "qdrant": {
1109
- const { QdrantProvider } = await import("./QdrantProvider-YNUNEOZH.mjs");
1109
+ const { QdrantProvider } = await import("./QdrantProvider-NGFXVDCF.mjs");
1110
1110
  return QdrantProvider;
1111
1111
  }
1112
1112
  case "chromadb": {
@@ -93,8 +93,15 @@ var QdrantProvider = class extends BaseVectorProvider {
93
93
  };
94
94
  await this.http.put(`/collections/${this.indexName}/points`, payload);
95
95
  }
96
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
97
96
  async query(vector, topK, namespace, _filter) {
97
+ const must = [];
98
+ if (namespace) {
99
+ must.push({ key: "namespace", match: { value: namespace } });
100
+ }
101
+ const sanitizedFilter = this.sanitizeFilter(_filter);
102
+ for (const [key, value] of Object.entries(sanitizedFilter)) {
103
+ must.push({ key: `metadata.${key}`, match: { value } });
104
+ }
98
105
  const payload = {
99
106
  vector,
100
107
  limit: topK,
@@ -103,12 +110,14 @@ var QdrantProvider = class extends BaseVectorProvider {
103
110
  hnsw_ef: this.config.options.efSearch || Math.max(topK * 20, 128),
104
111
  exact: false
105
112
  },
106
- filter: namespace ? {
107
- must: [{ key: "namespace", match: { value: namespace } }]
108
- } : void 0
113
+ filter: must.length > 0 ? { must } : void 0
109
114
  };
115
+ console.log(`[QdrantProvider] \u{1F50D} Searching "${this.indexName}" | Namespace: ${namespace || "default"} | TopK: ${topK}`);
116
+ if (must.length > (namespace ? 1 : 0)) {
117
+ console.log(`[QdrantProvider] \u{1F6E0} Filters:`, JSON.stringify(must.filter((m) => m.key !== "namespace")));
118
+ }
110
119
  const { data } = await this.http.post(`/collections/${this.indexName}/points/search`, payload);
111
- return (data.result || []).map((res) => {
120
+ const results = (data.result || []).map((res) => {
112
121
  var _a, _b;
113
122
  return {
114
123
  id: res["id"],
@@ -117,6 +126,12 @@ var QdrantProvider = class extends BaseVectorProvider {
117
126
  metadata: ((_b = res["payload"]) == null ? void 0 : _b.metadata) || {}
118
127
  };
119
128
  });
129
+ if (results.length === 0) {
130
+ console.warn(`[QdrantProvider] \u26A0\uFE0F Retrieval returned 0 results. Check if data exists in namespace "${namespace}"`);
131
+ } else {
132
+ console.log(`[QdrantProvider] \u2705 Found ${results.length} results (best score: ${results[0].score.toFixed(4)})`);
133
+ }
134
+ return results;
120
135
  }
121
136
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
122
137
  async delete(id, _namespace) {
@@ -1,3 +1,3 @@
1
- import '../RagConfig-BjC6zSTV.mjs';
2
- export { c as createChatHandler, d as createHealthHandler, e as createIngestHandler, f as createStreamHandler, g as createUploadHandler, s as sseErrorFrame, h as sseFrame, i as sseMetaFrame, j as sseTextFrame } from '../index-CU_fQq__.mjs';
1
+ import '../RagConfig-DVovvPmd.mjs';
2
+ export { c as createChatHandler, d as createHealthHandler, e as createIngestHandler, f as createStreamHandler, g as createUploadHandler, s as sseErrorFrame, h as sseFrame, i as sseMetaFrame, j as sseTextFrame } from '../index-D0_2f-43.mjs';
3
3
  import 'next/server';
@@ -1,3 +1,3 @@
1
- import '../RagConfig-BjC6zSTV.js';
2
- export { c as createChatHandler, d as createHealthHandler, e as createIngestHandler, f as createStreamHandler, g as createUploadHandler, s as sseErrorFrame, h as sseFrame, i as sseMetaFrame, j as sseTextFrame } from '../index-C3bLmWcR.js';
1
+ import '../RagConfig-DVovvPmd.js';
2
+ export { c as createChatHandler, d as createHealthHandler, e as createIngestHandler, f as createStreamHandler, g as createUploadHandler, s as sseErrorFrame, h as sseFrame, i as sseMetaFrame, j as sseTextFrame } from '../index-CQ0zQ7Zk.js';
3
3
  import 'next/server';
@@ -924,8 +924,15 @@ var init_QdrantProvider = __esm({
924
924
  };
925
925
  await this.http.put(`/collections/${this.indexName}/points`, payload);
926
926
  }
927
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
928
927
  async query(vector, topK, namespace, _filter) {
928
+ const must = [];
929
+ if (namespace) {
930
+ must.push({ key: "namespace", match: { value: namespace } });
931
+ }
932
+ const sanitizedFilter = this.sanitizeFilter(_filter);
933
+ for (const [key, value] of Object.entries(sanitizedFilter)) {
934
+ must.push({ key: `metadata.${key}`, match: { value } });
935
+ }
929
936
  const payload = {
930
937
  vector,
931
938
  limit: topK,
@@ -934,12 +941,14 @@ var init_QdrantProvider = __esm({
934
941
  hnsw_ef: this.config.options.efSearch || Math.max(topK * 20, 128),
935
942
  exact: false
936
943
  },
937
- filter: namespace ? {
938
- must: [{ key: "namespace", match: { value: namespace } }]
939
- } : void 0
944
+ filter: must.length > 0 ? { must } : void 0
940
945
  };
946
+ console.log(`[QdrantProvider] \u{1F50D} Searching "${this.indexName}" | Namespace: ${namespace || "default"} | TopK: ${topK}`);
947
+ if (must.length > (namespace ? 1 : 0)) {
948
+ console.log(`[QdrantProvider] \u{1F6E0} Filters:`, JSON.stringify(must.filter((m) => m.key !== "namespace")));
949
+ }
941
950
  const { data } = await this.http.post(`/collections/${this.indexName}/points/search`, payload);
942
- return (data.result || []).map((res) => {
951
+ const results = (data.result || []).map((res) => {
943
952
  var _a, _b;
944
953
  return {
945
954
  id: res["id"],
@@ -948,6 +957,12 @@ var init_QdrantProvider = __esm({
948
957
  metadata: ((_b = res["payload"]) == null ? void 0 : _b.metadata) || {}
949
958
  };
950
959
  });
960
+ if (results.length === 0) {
961
+ console.warn(`[QdrantProvider] \u26A0\uFE0F Retrieval returned 0 results. Check if data exists in namespace "${namespace}"`);
962
+ } else {
963
+ console.log(`[QdrantProvider] \u2705 Found ${results.length} results (best score: ${results[0].score.toFixed(4)})`);
964
+ }
965
+ return results;
951
966
  }
952
967
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
953
968
  async delete(id, _namespace) {
@@ -8,7 +8,7 @@ import {
8
8
  sseFrame,
9
9
  sseMetaFrame,
10
10
  sseTextFrame
11
- } from "../chunk-3GKA3PJF.mjs";
11
+ } from "../chunk-7S2SRQGL.mjs";
12
12
  import "../chunk-YLTMFW4M.mjs";
13
13
  import "../chunk-X4TOT24V.mjs";
14
14
  export {
@@ -1,4 +1,4 @@
1
- import { g as RagConfig, C as ChatMessage, b as ChatResponse, e as IngestDocument } from './RagConfig-BjC6zSTV.js';
1
+ import { g as RagConfig, C as ChatMessage, b as ChatResponse, e as IngestDocument } from './RagConfig-DVovvPmd.js';
2
2
  import { NextRequest, NextResponse } from 'next/server';
3
3
 
4
4
  interface ValidationError {
@@ -1,4 +1,4 @@
1
- import { g as RagConfig, C as ChatMessage, b as ChatResponse, e as IngestDocument } from './RagConfig-BjC6zSTV.mjs';
1
+ import { g as RagConfig, C as ChatMessage, b as ChatResponse, e as IngestDocument } from './RagConfig-DVovvPmd.mjs';
2
2
  import { NextRequest, NextResponse } from 'next/server';
3
3
 
4
4
  interface ValidationError {
package/dist/index.d.mts CHANGED
@@ -1,6 +1,6 @@
1
1
  import React$1, { ReactNode } from 'react';
2
- import { C as ChatMessage, V as VectorMatch, U as UIConfig } from './RagConfig-BjC6zSTV.mjs';
3
- export { a as ChatOptions, b as ChatResponse, E as EmbedOptions, c as EmbeddingConfig, d as EmbeddingProvider, I as ILLMProvider, e as IngestDocument, L as LLMConfig, f as LLMProvider, R as RAGConfig, g as RagConfig, h as UpsertDocument, i as VectorDBConfig, j as VectorDBProvider } from './RagConfig-BjC6zSTV.mjs';
2
+ import { C as ChatMessage, V as VectorMatch, U as UIConfig } from './RagConfig-DVovvPmd.mjs';
3
+ export { a as ChatOptions, b as ChatResponse, E as EmbedOptions, c as EmbeddingConfig, d as EmbeddingProvider, I as ILLMProvider, e as IngestDocument, L as LLMConfig, f as LLMProvider, R as RAGConfig, g as RagConfig, h as UpsertDocument, i as VectorDBConfig, j as VectorDBProvider } from './RagConfig-DVovvPmd.mjs';
4
4
  export { C as Chunk, a as ChunkOptions } from './DocumentChunker-Dh9TvmGG.mjs';
5
5
 
6
6
  interface ChatWidgetProps {
@@ -18,8 +18,18 @@ interface ChatWindowProps {
18
18
  onClose?: () => void;
19
19
  /** Whether to show a close (X) button in the header */
20
20
  showClose?: boolean;
21
+ /** Called when the user starts dragging the resize handle */
22
+ onResizeStart?: (e: React$1.MouseEvent) => void;
23
+ /** Called when the user clicks the reset size button */
24
+ onResetResize?: () => void;
25
+ /** Whether the window has been resized from its default */
26
+ isResized?: boolean;
27
+ /** Called when the user clicks the maximize/minimize button */
28
+ onMaximize?: () => void;
29
+ /** Whether the window is currently maximized */
30
+ isMaximized?: boolean;
21
31
  }
22
- declare function ChatWindow({ className, style, onClose, showClose }: ChatWindowProps): React$1.JSX.Element;
32
+ declare function ChatWindow({ className, style, onClose, showClose, onResizeStart, onResetResize, isResized, onMaximize, isMaximized }: ChatWindowProps): React$1.JSX.Element;
23
33
 
24
34
  interface DocumentUploadProps {
25
35
  /** Optional namespace for the upload */
package/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import React$1, { ReactNode } from 'react';
2
- import { C as ChatMessage, V as VectorMatch, U as UIConfig } from './RagConfig-BjC6zSTV.js';
3
- export { a as ChatOptions, b as ChatResponse, E as EmbedOptions, c as EmbeddingConfig, d as EmbeddingProvider, I as ILLMProvider, e as IngestDocument, L as LLMConfig, f as LLMProvider, R as RAGConfig, g as RagConfig, h as UpsertDocument, i as VectorDBConfig, j as VectorDBProvider } from './RagConfig-BjC6zSTV.js';
2
+ import { C as ChatMessage, V as VectorMatch, U as UIConfig } from './RagConfig-DVovvPmd.js';
3
+ export { a as ChatOptions, b as ChatResponse, E as EmbedOptions, c as EmbeddingConfig, d as EmbeddingProvider, I as ILLMProvider, e as IngestDocument, L as LLMConfig, f as LLMProvider, R as RAGConfig, g as RagConfig, h as UpsertDocument, i as VectorDBConfig, j as VectorDBProvider } from './RagConfig-DVovvPmd.js';
4
4
  export { C as Chunk, a as ChunkOptions } from './DocumentChunker-Dh9TvmGG.js';
5
5
 
6
6
  interface ChatWidgetProps {
@@ -18,8 +18,18 @@ interface ChatWindowProps {
18
18
  onClose?: () => void;
19
19
  /** Whether to show a close (X) button in the header */
20
20
  showClose?: boolean;
21
+ /** Called when the user starts dragging the resize handle */
22
+ onResizeStart?: (e: React$1.MouseEvent) => void;
23
+ /** Called when the user clicks the reset size button */
24
+ onResetResize?: () => void;
25
+ /** Whether the window has been resized from its default */
26
+ isResized?: boolean;
27
+ /** Called when the user clicks the maximize/minimize button */
28
+ onMaximize?: () => void;
29
+ /** Whether the window is currently maximized */
30
+ isMaximized?: boolean;
21
31
  }
22
- declare function ChatWindow({ className, style, onClose, showClose }: ChatWindowProps): React$1.JSX.Element;
32
+ declare function ChatWindow({ className, style, onClose, showClose, onResizeStart, onResetResize, isResized, onMaximize, isMaximized }: ChatWindowProps): React$1.JSX.Element;
23
33
 
24
34
  interface DocumentUploadProps {
25
35
  /** Optional namespace for the upload */
package/dist/index.js CHANGED
@@ -166,7 +166,8 @@ var defaultConfig = {
166
166
  poweredBy: "RAG",
167
167
  visualStyle: "glass",
168
168
  borderRadius: "xl",
169
- allowUpload: true
169
+ allowUpload: true,
170
+ allowResize: true
170
171
  }
171
172
  };
172
173
  var ConfigContext = (0, import_react3.createContext)(defaultConfig);
@@ -390,7 +391,17 @@ var CHAT_SUGGESTIONS = [
390
391
  ];
391
392
 
392
393
  // src/components/ChatWindow.tsx
393
- function ChatWindow({ className = "", style, onClose, showClose = false }) {
394
+ function ChatWindow({
395
+ className = "",
396
+ style,
397
+ onClose,
398
+ showClose = false,
399
+ onResizeStart,
400
+ onResetResize,
401
+ isResized = false,
402
+ onMaximize,
403
+ isMaximized = false
404
+ }) {
394
405
  var _a;
395
406
  const { ui, projectId } = useConfig();
396
407
  const [input, setInput] = (0, import_react5.useState)("");
@@ -434,9 +445,18 @@ function ChatWindow({ className = "", style, onClose, showClose = false }) {
434
445
  return /* @__PURE__ */ import_react5.default.createElement(
435
446
  "div",
436
447
  {
437
- className: `flex flex-col border border-slate-200 dark:border-white/10 shadow-2xl transition-all duration-300 ${currentRadius} ${isGlass ? "bg-white/90 dark:bg-[#0f0f1a]/90 backdrop-blur-xl" : "bg-white dark:bg-[#0f0f1a]"} ${className}`,
448
+ className: `relative flex flex-col border border-slate-200 dark:border-white/10 shadow-2xl transition-all duration-300 ${currentRadius} ${isGlass ? "bg-white/90 dark:bg-[#0f0f1a]/90 backdrop-blur-xl" : "bg-white dark:bg-[#0f0f1a]"} ${className}`,
438
449
  style: __spreadValues({ "--primary": ui.primaryColor, "--accent": ui.accentColor }, style)
439
450
  },
451
+ onResizeStart && /* @__PURE__ */ import_react5.default.createElement(
452
+ "div",
453
+ {
454
+ onMouseDown: onResizeStart,
455
+ className: "absolute top-0 left-0 w-6 h-6 cursor-nw-resize z-[100] group flex items-start justify-start p-1",
456
+ title: "Drag to resize"
457
+ },
458
+ /* @__PURE__ */ import_react5.default.createElement("div", { className: "w-2.5 h-2.5 border-t-2 border-l-2 border-slate-300 dark:border-white/20 group-hover:border-slate-500 dark:group-hover:border-white/50 transition-colors rounded-tl-[2px]" })
459
+ ),
440
460
  /* @__PURE__ */ import_react5.default.createElement(
441
461
  "div",
442
462
  {
@@ -454,23 +474,39 @@ function ChatWindow({ className = "", style, onClose, showClose = false }) {
454
474
  },
455
475
  /* @__PURE__ */ import_react5.default.createElement(import_lucide_react3.Bot, { className: "w-5 h-5 text-white" })
456
476
  ), /* @__PURE__ */ import_react5.default.createElement("div", null, /* @__PURE__ */ import_react5.default.createElement("h2", { className: "text-slate-900 dark:text-white font-semibold text-sm leading-tight" }, ui.title), ui.poweredBy && /* @__PURE__ */ import_react5.default.createElement("p", { className: "text-slate-500 dark:text-white/40 text-[11px] leading-tight" }, `Powered by ${ui.poweredBy}`))),
457
- /* @__PURE__ */ import_react5.default.createElement("div", { className: "flex items-center gap-2" }, /* @__PURE__ */ import_react5.default.createElement("span", { className: "flex items-center gap-1 text-[10px] text-emerald-500 dark:text-emerald-400" }, /* @__PURE__ */ import_react5.default.createElement("span", { className: "w-1.5 h-1.5 rounded-full bg-emerald-500 dark:bg-emerald-400 animate-pulse" }), "Online"), mounted && messages.length > 0 && /* @__PURE__ */ import_react5.default.createElement(
477
+ /* @__PURE__ */ import_react5.default.createElement("div", { className: "flex items-center gap-1.5" }, /* @__PURE__ */ import_react5.default.createElement("span", { className: "flex items-center gap-1 text-[10px] text-emerald-500 dark:text-emerald-400 mr-2" }, /* @__PURE__ */ import_react5.default.createElement("span", { className: "w-1.5 h-1.5 rounded-full bg-emerald-500 dark:bg-emerald-400 animate-pulse" }), "Online"), /* @__PURE__ */ import_react5.default.createElement("div", { className: "flex items-center bg-slate-100/50 dark:bg-white/5 rounded-lg p-0.5 border border-slate-200/50 dark:border-white/5" }, mounted && messages.length > 0 && /* @__PURE__ */ import_react5.default.createElement(
458
478
  "button",
459
479
  {
460
480
  onClick: clearHistory,
461
481
  title: "Clear conversation",
462
- className: "w-7 h-7 rounded-lg flex items-center justify-center text-slate-400 hover:text-slate-600 dark:text-white/40 dark:hover:text-white/70 hover:bg-slate-100 dark:hover:bg-white/10 transition-all cursor-pointer"
482
+ className: "w-7 h-7 rounded-md flex items-center justify-center text-slate-400 hover:text-rose-500 dark:text-white/40 dark:hover:text-rose-400 hover:bg-white dark:hover:bg-white/10 transition-all cursor-pointer"
463
483
  },
464
484
  /* @__PURE__ */ import_react5.default.createElement(import_lucide_react3.Trash2, { className: "w-3.5 h-3.5" })
485
+ ), isResized && onResetResize && /* @__PURE__ */ import_react5.default.createElement(
486
+ "button",
487
+ {
488
+ onClick: onResetResize,
489
+ title: "Reset to default size",
490
+ className: "w-7 h-7 rounded-md flex items-center justify-center text-slate-400 hover:text-blue-500 dark:text-white/40 dark:hover:text-blue-400 hover:bg-white dark:hover:bg-white/10 transition-all cursor-pointer"
491
+ },
492
+ /* @__PURE__ */ import_react5.default.createElement(import_lucide_react3.RotateCcw, { className: "w-3.5 h-3.5" })
493
+ ), onMaximize && /* @__PURE__ */ import_react5.default.createElement(
494
+ "button",
495
+ {
496
+ onClick: onMaximize,
497
+ title: isMaximized ? "Minimize" : "Maximize",
498
+ className: "w-7 h-7 rounded-md flex items-center justify-center text-slate-400 hover:text-blue-500 dark:text-white/40 dark:hover:text-blue-400 hover:bg-white dark:hover:bg-white/10 transition-all cursor-pointer"
499
+ },
500
+ isMaximized ? /* @__PURE__ */ import_react5.default.createElement(import_lucide_react3.Minimize2, { className: "w-3.5 h-3.5" }) : /* @__PURE__ */ import_react5.default.createElement(import_lucide_react3.Maximize2, { className: "w-3.5 h-3.5" })
465
501
  ), showClose && onClose && /* @__PURE__ */ import_react5.default.createElement(
466
502
  "button",
467
503
  {
468
504
  onClick: onClose,
469
505
  title: "Close chat",
470
- className: "w-7 h-7 rounded-lg flex items-center justify-center text-slate-400 hover:text-slate-600 dark:text-white/40 dark:hover:text-white/70 hover:bg-slate-100 dark:hover:bg-white/10 transition-all cursor-pointer"
506
+ className: "w-7 h-7 rounded-md flex items-center justify-center text-slate-400 hover:text-slate-600 dark:text-white/40 dark:hover:text-white/70 hover:bg-white dark:hover:bg-white/10 transition-all cursor-pointer"
471
507
  },
472
508
  /* @__PURE__ */ import_react5.default.createElement(import_lucide_react3.X, { className: "w-4 h-4" })
473
- ))
509
+ )))
474
510
  ),
475
511
  /* @__PURE__ */ import_react5.default.createElement("div", { className: "flex-1 overflow-y-auto px-4 py-4 space-y-5 scrollbar-thin scrollbar-thumb-slate-200 dark:scrollbar-thumb-white/10 scrollbar-track-transparent min-h-0 bg-slate-50 dark:bg-transparent" }, !mounted || isEmpty ? (
476
512
  /* Welcome state */
@@ -545,10 +581,15 @@ function ChatWindow({ className = "", style, onClose, showClose = false }) {
545
581
  }
546
582
 
547
583
  // src/components/ChatWidget.tsx
584
+ var DEFAULT_DIMENSIONS = { width: 380, height: 600 };
548
585
  function ChatWidget({ position = "bottom-right" }) {
549
586
  const { ui } = useConfig();
550
587
  const [isOpen, setIsOpen] = (0, import_react6.useState)(false);
551
588
  const [hasUnread, setHasUnread] = (0, import_react6.useState)(false);
589
+ const [dimensions, setDimensions] = (0, import_react6.useState)(DEFAULT_DIMENSIONS);
590
+ const [isResizing, setIsResizing] = (0, import_react6.useState)(false);
591
+ const [isMaximized, setIsMaximized] = (0, import_react6.useState)(false);
592
+ const [prevDimensions, setPrevDimensions] = (0, import_react6.useState)(DEFAULT_DIMENSIONS);
552
593
  if (ui.showWidget === false) return null;
553
594
  const positionClass = position === "bottom-left" ? "bottom-6 left-6" : "bottom-6 right-6";
554
595
  const windowPositionClass = position === "bottom-left" ? "bottom-20 left-6" : "bottom-20 right-6";
@@ -557,18 +598,68 @@ function ChatWidget({ position = "bottom-right" }) {
557
598
  setIsOpen(true);
558
599
  setHasUnread(false);
559
600
  };
601
+ const handleResizeStart = (e) => {
602
+ e.preventDefault();
603
+ setIsResizing(true);
604
+ const startX = e.clientX;
605
+ const startY = e.clientY;
606
+ const startWidth = dimensions.width;
607
+ const startHeight = dimensions.height;
608
+ const onMouseMove = (moveEvent) => {
609
+ const deltaX = startX - moveEvent.clientX;
610
+ const deltaY = startY - moveEvent.clientY;
611
+ setDimensions({
612
+ width: Math.max(320, Math.min(startWidth + deltaX, window.innerWidth - 48)),
613
+ height: Math.max(400, Math.min(startHeight + deltaY, window.innerHeight - 100))
614
+ });
615
+ };
616
+ const onMouseUp = () => {
617
+ setIsResizing(false);
618
+ window.removeEventListener("mousemove", onMouseMove);
619
+ window.removeEventListener("mouseup", onMouseUp);
620
+ };
621
+ window.addEventListener("mousemove", onMouseMove);
622
+ window.addEventListener("mouseup", onMouseUp);
623
+ };
624
+ const handleResetResize = () => {
625
+ setDimensions(DEFAULT_DIMENSIONS);
626
+ setIsMaximized(false);
627
+ };
628
+ const handleMaximize = () => {
629
+ if (isMaximized) {
630
+ setDimensions(prevDimensions);
631
+ setIsMaximized(false);
632
+ } else {
633
+ setPrevDimensions(dimensions);
634
+ setDimensions({
635
+ width: Math.min(800, window.innerWidth - 48),
636
+ height: Math.min(800, window.innerHeight - 100)
637
+ });
638
+ setIsMaximized(true);
639
+ }
640
+ };
641
+ const isResized = dimensions.width !== DEFAULT_DIMENSIONS.width || dimensions.height !== DEFAULT_DIMENSIONS.height;
560
642
  return /* @__PURE__ */ import_react6.default.createElement(import_react6.default.Fragment, null, /* @__PURE__ */ import_react6.default.createElement(
561
643
  "div",
562
644
  {
563
- className: `fixed z-[9998] w-[380px] max-w-[calc(100vw-3rem)] transition-all duration-300 ease-in-out ${windowPositionClass} ${isOpen ? "opacity-100 translate-y-0 pointer-events-auto" : "opacity-0 translate-y-4 pointer-events-none"}`,
564
- style: { height: "600px", maxHeight: "calc(100vh - 6rem)" }
645
+ className: `fixed z-[9998] max-w-[calc(100vw-3rem)] ease-in-out ${windowPositionClass} ${isOpen ? "opacity-100 translate-y-0 pointer-events-auto" : "opacity-0 translate-y-4 pointer-events-none"} ${isResizing ? "" : "transition-all duration-300"}`,
646
+ style: {
647
+ width: `${dimensions.width}px`,
648
+ height: `${dimensions.height}px`,
649
+ maxHeight: "calc(100vh - 6rem)"
650
+ }
565
651
  },
566
652
  /* @__PURE__ */ import_react6.default.createElement(
567
653
  ChatWindow,
568
654
  {
569
655
  className: "h-full relative z-10",
570
656
  showClose: true,
571
- onClose: () => setIsOpen(false)
657
+ onClose: () => setIsOpen(false),
658
+ onResizeStart: ui.allowResize !== false ? handleResizeStart : void 0,
659
+ onResetResize: ui.allowResize !== false ? handleResetResize : void 0,
660
+ isResized,
661
+ onMaximize: ui.allowResize !== false ? handleMaximize : void 0,
662
+ isMaximized
572
663
  }
573
664
  ),
574
665
  /* @__PURE__ */ import_react6.default.createElement(
package/dist/index.mjs CHANGED
@@ -18,7 +18,10 @@ import {
18
18
  X,
19
19
  Sparkles,
20
20
  ArrowUp,
21
- TriangleAlert
21
+ TriangleAlert,
22
+ RotateCcw,
23
+ Maximize2,
24
+ Minimize2
22
25
  } from "lucide-react";
23
26
 
24
27
  // src/components/MessageBubble.tsx
@@ -105,7 +108,8 @@ var defaultConfig = {
105
108
  poweredBy: "RAG",
106
109
  visualStyle: "glass",
107
110
  borderRadius: "xl",
108
- allowUpload: true
111
+ allowUpload: true,
112
+ allowResize: true
109
113
  }
110
114
  };
111
115
  var ConfigContext = createContext(defaultConfig);
@@ -329,7 +333,17 @@ var CHAT_SUGGESTIONS = [
329
333
  ];
330
334
 
331
335
  // src/components/ChatWindow.tsx
332
- function ChatWindow({ className = "", style, onClose, showClose = false }) {
336
+ function ChatWindow({
337
+ className = "",
338
+ style,
339
+ onClose,
340
+ showClose = false,
341
+ onResizeStart,
342
+ onResetResize,
343
+ isResized = false,
344
+ onMaximize,
345
+ isMaximized = false
346
+ }) {
333
347
  var _a;
334
348
  const { ui, projectId } = useConfig();
335
349
  const [input, setInput] = useState3("");
@@ -373,9 +387,18 @@ function ChatWindow({ className = "", style, onClose, showClose = false }) {
373
387
  return /* @__PURE__ */ React5.createElement(
374
388
  "div",
375
389
  {
376
- className: `flex flex-col border border-slate-200 dark:border-white/10 shadow-2xl transition-all duration-300 ${currentRadius} ${isGlass ? "bg-white/90 dark:bg-[#0f0f1a]/90 backdrop-blur-xl" : "bg-white dark:bg-[#0f0f1a]"} ${className}`,
390
+ className: `relative flex flex-col border border-slate-200 dark:border-white/10 shadow-2xl transition-all duration-300 ${currentRadius} ${isGlass ? "bg-white/90 dark:bg-[#0f0f1a]/90 backdrop-blur-xl" : "bg-white dark:bg-[#0f0f1a]"} ${className}`,
377
391
  style: __spreadValues({ "--primary": ui.primaryColor, "--accent": ui.accentColor }, style)
378
392
  },
393
+ onResizeStart && /* @__PURE__ */ React5.createElement(
394
+ "div",
395
+ {
396
+ onMouseDown: onResizeStart,
397
+ className: "absolute top-0 left-0 w-6 h-6 cursor-nw-resize z-[100] group flex items-start justify-start p-1",
398
+ title: "Drag to resize"
399
+ },
400
+ /* @__PURE__ */ React5.createElement("div", { className: "w-2.5 h-2.5 border-t-2 border-l-2 border-slate-300 dark:border-white/20 group-hover:border-slate-500 dark:group-hover:border-white/50 transition-colors rounded-tl-[2px]" })
401
+ ),
379
402
  /* @__PURE__ */ React5.createElement(
380
403
  "div",
381
404
  {
@@ -393,23 +416,39 @@ function ChatWindow({ className = "", style, onClose, showClose = false }) {
393
416
  },
394
417
  /* @__PURE__ */ React5.createElement(Bot2, { className: "w-5 h-5 text-white" })
395
418
  ), /* @__PURE__ */ React5.createElement("div", null, /* @__PURE__ */ React5.createElement("h2", { className: "text-slate-900 dark:text-white font-semibold text-sm leading-tight" }, ui.title), ui.poweredBy && /* @__PURE__ */ React5.createElement("p", { className: "text-slate-500 dark:text-white/40 text-[11px] leading-tight" }, `Powered by ${ui.poweredBy}`))),
396
- /* @__PURE__ */ React5.createElement("div", { className: "flex items-center gap-2" }, /* @__PURE__ */ React5.createElement("span", { className: "flex items-center gap-1 text-[10px] text-emerald-500 dark:text-emerald-400" }, /* @__PURE__ */ React5.createElement("span", { className: "w-1.5 h-1.5 rounded-full bg-emerald-500 dark:bg-emerald-400 animate-pulse" }), "Online"), mounted && messages.length > 0 && /* @__PURE__ */ React5.createElement(
419
+ /* @__PURE__ */ React5.createElement("div", { className: "flex items-center gap-1.5" }, /* @__PURE__ */ React5.createElement("span", { className: "flex items-center gap-1 text-[10px] text-emerald-500 dark:text-emerald-400 mr-2" }, /* @__PURE__ */ React5.createElement("span", { className: "w-1.5 h-1.5 rounded-full bg-emerald-500 dark:bg-emerald-400 animate-pulse" }), "Online"), /* @__PURE__ */ React5.createElement("div", { className: "flex items-center bg-slate-100/50 dark:bg-white/5 rounded-lg p-0.5 border border-slate-200/50 dark:border-white/5" }, mounted && messages.length > 0 && /* @__PURE__ */ React5.createElement(
397
420
  "button",
398
421
  {
399
422
  onClick: clearHistory,
400
423
  title: "Clear conversation",
401
- className: "w-7 h-7 rounded-lg flex items-center justify-center text-slate-400 hover:text-slate-600 dark:text-white/40 dark:hover:text-white/70 hover:bg-slate-100 dark:hover:bg-white/10 transition-all cursor-pointer"
424
+ className: "w-7 h-7 rounded-md flex items-center justify-center text-slate-400 hover:text-rose-500 dark:text-white/40 dark:hover:text-rose-400 hover:bg-white dark:hover:bg-white/10 transition-all cursor-pointer"
402
425
  },
403
426
  /* @__PURE__ */ React5.createElement(Trash2, { className: "w-3.5 h-3.5" })
427
+ ), isResized && onResetResize && /* @__PURE__ */ React5.createElement(
428
+ "button",
429
+ {
430
+ onClick: onResetResize,
431
+ title: "Reset to default size",
432
+ className: "w-7 h-7 rounded-md flex items-center justify-center text-slate-400 hover:text-blue-500 dark:text-white/40 dark:hover:text-blue-400 hover:bg-white dark:hover:bg-white/10 transition-all cursor-pointer"
433
+ },
434
+ /* @__PURE__ */ React5.createElement(RotateCcw, { className: "w-3.5 h-3.5" })
435
+ ), onMaximize && /* @__PURE__ */ React5.createElement(
436
+ "button",
437
+ {
438
+ onClick: onMaximize,
439
+ title: isMaximized ? "Minimize" : "Maximize",
440
+ className: "w-7 h-7 rounded-md flex items-center justify-center text-slate-400 hover:text-blue-500 dark:text-white/40 dark:hover:text-blue-400 hover:bg-white dark:hover:bg-white/10 transition-all cursor-pointer"
441
+ },
442
+ isMaximized ? /* @__PURE__ */ React5.createElement(Minimize2, { className: "w-3.5 h-3.5" }) : /* @__PURE__ */ React5.createElement(Maximize2, { className: "w-3.5 h-3.5" })
404
443
  ), showClose && onClose && /* @__PURE__ */ React5.createElement(
405
444
  "button",
406
445
  {
407
446
  onClick: onClose,
408
447
  title: "Close chat",
409
- className: "w-7 h-7 rounded-lg flex items-center justify-center text-slate-400 hover:text-slate-600 dark:text-white/40 dark:hover:text-white/70 hover:bg-slate-100 dark:hover:bg-white/10 transition-all cursor-pointer"
448
+ className: "w-7 h-7 rounded-md flex items-center justify-center text-slate-400 hover:text-slate-600 dark:text-white/40 dark:hover:text-white/70 hover:bg-white dark:hover:bg-white/10 transition-all cursor-pointer"
410
449
  },
411
450
  /* @__PURE__ */ React5.createElement(X, { className: "w-4 h-4" })
412
- ))
451
+ )))
413
452
  ),
414
453
  /* @__PURE__ */ React5.createElement("div", { className: "flex-1 overflow-y-auto px-4 py-4 space-y-5 scrollbar-thin scrollbar-thumb-slate-200 dark:scrollbar-thumb-white/10 scrollbar-track-transparent min-h-0 bg-slate-50 dark:bg-transparent" }, !mounted || isEmpty ? (
415
454
  /* Welcome state */
@@ -484,10 +523,15 @@ function ChatWindow({ className = "", style, onClose, showClose = false }) {
484
523
  }
485
524
 
486
525
  // src/components/ChatWidget.tsx
526
+ var DEFAULT_DIMENSIONS = { width: 380, height: 600 };
487
527
  function ChatWidget({ position = "bottom-right" }) {
488
528
  const { ui } = useConfig();
489
529
  const [isOpen, setIsOpen] = useState4(false);
490
530
  const [hasUnread, setHasUnread] = useState4(false);
531
+ const [dimensions, setDimensions] = useState4(DEFAULT_DIMENSIONS);
532
+ const [isResizing, setIsResizing] = useState4(false);
533
+ const [isMaximized, setIsMaximized] = useState4(false);
534
+ const [prevDimensions, setPrevDimensions] = useState4(DEFAULT_DIMENSIONS);
491
535
  if (ui.showWidget === false) return null;
492
536
  const positionClass = position === "bottom-left" ? "bottom-6 left-6" : "bottom-6 right-6";
493
537
  const windowPositionClass = position === "bottom-left" ? "bottom-20 left-6" : "bottom-20 right-6";
@@ -496,18 +540,68 @@ function ChatWidget({ position = "bottom-right" }) {
496
540
  setIsOpen(true);
497
541
  setHasUnread(false);
498
542
  };
543
+ const handleResizeStart = (e) => {
544
+ e.preventDefault();
545
+ setIsResizing(true);
546
+ const startX = e.clientX;
547
+ const startY = e.clientY;
548
+ const startWidth = dimensions.width;
549
+ const startHeight = dimensions.height;
550
+ const onMouseMove = (moveEvent) => {
551
+ const deltaX = startX - moveEvent.clientX;
552
+ const deltaY = startY - moveEvent.clientY;
553
+ setDimensions({
554
+ width: Math.max(320, Math.min(startWidth + deltaX, window.innerWidth - 48)),
555
+ height: Math.max(400, Math.min(startHeight + deltaY, window.innerHeight - 100))
556
+ });
557
+ };
558
+ const onMouseUp = () => {
559
+ setIsResizing(false);
560
+ window.removeEventListener("mousemove", onMouseMove);
561
+ window.removeEventListener("mouseup", onMouseUp);
562
+ };
563
+ window.addEventListener("mousemove", onMouseMove);
564
+ window.addEventListener("mouseup", onMouseUp);
565
+ };
566
+ const handleResetResize = () => {
567
+ setDimensions(DEFAULT_DIMENSIONS);
568
+ setIsMaximized(false);
569
+ };
570
+ const handleMaximize = () => {
571
+ if (isMaximized) {
572
+ setDimensions(prevDimensions);
573
+ setIsMaximized(false);
574
+ } else {
575
+ setPrevDimensions(dimensions);
576
+ setDimensions({
577
+ width: Math.min(800, window.innerWidth - 48),
578
+ height: Math.min(800, window.innerHeight - 100)
579
+ });
580
+ setIsMaximized(true);
581
+ }
582
+ };
583
+ const isResized = dimensions.width !== DEFAULT_DIMENSIONS.width || dimensions.height !== DEFAULT_DIMENSIONS.height;
499
584
  return /* @__PURE__ */ React6.createElement(React6.Fragment, null, /* @__PURE__ */ React6.createElement(
500
585
  "div",
501
586
  {
502
- className: `fixed z-[9998] w-[380px] max-w-[calc(100vw-3rem)] transition-all duration-300 ease-in-out ${windowPositionClass} ${isOpen ? "opacity-100 translate-y-0 pointer-events-auto" : "opacity-0 translate-y-4 pointer-events-none"}`,
503
- style: { height: "600px", maxHeight: "calc(100vh - 6rem)" }
587
+ className: `fixed z-[9998] max-w-[calc(100vw-3rem)] ease-in-out ${windowPositionClass} ${isOpen ? "opacity-100 translate-y-0 pointer-events-auto" : "opacity-0 translate-y-4 pointer-events-none"} ${isResizing ? "" : "transition-all duration-300"}`,
588
+ style: {
589
+ width: `${dimensions.width}px`,
590
+ height: `${dimensions.height}px`,
591
+ maxHeight: "calc(100vh - 6rem)"
592
+ }
504
593
  },
505
594
  /* @__PURE__ */ React6.createElement(
506
595
  ChatWindow,
507
596
  {
508
597
  className: "h-full relative z-10",
509
598
  showClose: true,
510
- onClose: () => setIsOpen(false)
599
+ onClose: () => setIsOpen(false),
600
+ onResizeStart: ui.allowResize !== false ? handleResizeStart : void 0,
601
+ onResetResize: ui.allowResize !== false ? handleResetResize : void 0,
602
+ isResized,
603
+ onMaximize: ui.allowResize !== false ? handleMaximize : void 0,
604
+ isMaximized
511
605
  }
512
606
  ),
513
607
  /* @__PURE__ */ React6.createElement(
package/dist/server.d.mts CHANGED
@@ -1,7 +1,7 @@
1
- import { i as VectorDBConfig, L as LLMConfig, c as EmbeddingConfig, g as RagConfig, e as IngestDocument, C as ChatMessage, b as ChatResponse, k as RetrievalResult, h as UpsertDocument, V as VectorMatch, G as GraphDBConfig, l as GraphNode, m as Edge, n as GraphSearchResult, I as ILLMProvider, j as VectorDBProvider, f as LLMProvider, d as EmbeddingProvider, R as RAGConfig, U as UIConfig, a as ChatOptions, E as EmbedOptions } from './RagConfig-BjC6zSTV.mjs';
1
+ import { i as VectorDBConfig, L as LLMConfig, c as EmbeddingConfig, g as RagConfig, e as IngestDocument, C as ChatMessage, b as ChatResponse, k as RetrievalResult, h as UpsertDocument, V as VectorMatch, G as GraphDBConfig, l as GraphNode, m as Edge, n as GraphSearchResult, I as ILLMProvider, j as VectorDBProvider, f as LLMProvider, d as EmbeddingProvider, R as RAGConfig, U as UIConfig, a as ChatOptions, E as EmbedOptions } from './RagConfig-DVovvPmd.mjs';
2
2
  export { C as Chunk, a as ChunkOptions, D as DocumentChunker } from './DocumentChunker-Dh9TvmGG.mjs';
3
- import { H as HealthCheckResult, I as IProviderValidator, a as IProviderHealthChecker } from './index-CU_fQq__.mjs';
4
- export { C as ConfigValidator, V as ValidationError, b as VectorPlugin, c as createChatHandler, d as createHealthHandler, e as createIngestHandler, f as createStreamHandler, g as createUploadHandler, s as sseErrorFrame, h as sseFrame, i as sseMetaFrame, j as sseTextFrame } from './index-CU_fQq__.mjs';
3
+ import { H as HealthCheckResult, I as IProviderValidator, a as IProviderHealthChecker } from './index-D0_2f-43.mjs';
4
+ export { C as ConfigValidator, V as ValidationError, b as VectorPlugin, c as createChatHandler, d as createHealthHandler, e as createIngestHandler, f as createStreamHandler, g as createUploadHandler, s as sseErrorFrame, h as sseFrame, i as sseMetaFrame, j as sseTextFrame } from './index-D0_2f-43.mjs';
5
5
  import 'next/server';
6
6
 
7
7
  /**
package/dist/server.d.ts CHANGED
@@ -1,7 +1,7 @@
1
- import { i as VectorDBConfig, L as LLMConfig, c as EmbeddingConfig, g as RagConfig, e as IngestDocument, C as ChatMessage, b as ChatResponse, k as RetrievalResult, h as UpsertDocument, V as VectorMatch, G as GraphDBConfig, l as GraphNode, m as Edge, n as GraphSearchResult, I as ILLMProvider, j as VectorDBProvider, f as LLMProvider, d as EmbeddingProvider, R as RAGConfig, U as UIConfig, a as ChatOptions, E as EmbedOptions } from './RagConfig-BjC6zSTV.js';
1
+ import { i as VectorDBConfig, L as LLMConfig, c as EmbeddingConfig, g as RagConfig, e as IngestDocument, C as ChatMessage, b as ChatResponse, k as RetrievalResult, h as UpsertDocument, V as VectorMatch, G as GraphDBConfig, l as GraphNode, m as Edge, n as GraphSearchResult, I as ILLMProvider, j as VectorDBProvider, f as LLMProvider, d as EmbeddingProvider, R as RAGConfig, U as UIConfig, a as ChatOptions, E as EmbedOptions } from './RagConfig-DVovvPmd.js';
2
2
  export { C as Chunk, a as ChunkOptions, D as DocumentChunker } from './DocumentChunker-Dh9TvmGG.js';
3
- import { H as HealthCheckResult, I as IProviderValidator, a as IProviderHealthChecker } from './index-C3bLmWcR.js';
4
- export { C as ConfigValidator, V as ValidationError, b as VectorPlugin, c as createChatHandler, d as createHealthHandler, e as createIngestHandler, f as createStreamHandler, g as createUploadHandler, s as sseErrorFrame, h as sseFrame, i as sseMetaFrame, j as sseTextFrame } from './index-C3bLmWcR.js';
3
+ import { H as HealthCheckResult, I as IProviderValidator, a as IProviderHealthChecker } from './index-CQ0zQ7Zk.js';
4
+ export { C as ConfigValidator, V as ValidationError, b as VectorPlugin, c as createChatHandler, d as createHealthHandler, e as createIngestHandler, f as createStreamHandler, g as createUploadHandler, s as sseErrorFrame, h as sseFrame, i as sseMetaFrame, j as sseTextFrame } from './index-CQ0zQ7Zk.js';
5
5
  import 'next/server';
6
6
 
7
7
  /**
package/dist/server.js CHANGED
@@ -936,8 +936,15 @@ var init_QdrantProvider = __esm({
936
936
  };
937
937
  await this.http.put(`/collections/${this.indexName}/points`, payload);
938
938
  }
939
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
940
939
  async query(vector, topK, namespace, _filter) {
940
+ const must = [];
941
+ if (namespace) {
942
+ must.push({ key: "namespace", match: { value: namespace } });
943
+ }
944
+ const sanitizedFilter = this.sanitizeFilter(_filter);
945
+ for (const [key, value] of Object.entries(sanitizedFilter)) {
946
+ must.push({ key: `metadata.${key}`, match: { value } });
947
+ }
941
948
  const payload = {
942
949
  vector,
943
950
  limit: topK,
@@ -946,12 +953,14 @@ var init_QdrantProvider = __esm({
946
953
  hnsw_ef: this.config.options.efSearch || Math.max(topK * 20, 128),
947
954
  exact: false
948
955
  },
949
- filter: namespace ? {
950
- must: [{ key: "namespace", match: { value: namespace } }]
951
- } : void 0
956
+ filter: must.length > 0 ? { must } : void 0
952
957
  };
958
+ console.log(`[QdrantProvider] \u{1F50D} Searching "${this.indexName}" | Namespace: ${namespace || "default"} | TopK: ${topK}`);
959
+ if (must.length > (namespace ? 1 : 0)) {
960
+ console.log(`[QdrantProvider] \u{1F6E0} Filters:`, JSON.stringify(must.filter((m) => m.key !== "namespace")));
961
+ }
953
962
  const { data } = await this.http.post(`/collections/${this.indexName}/points/search`, payload);
954
- return (data.result || []).map((res) => {
963
+ const results = (data.result || []).map((res) => {
955
964
  var _a, _b;
956
965
  return {
957
966
  id: res["id"],
@@ -960,6 +969,12 @@ var init_QdrantProvider = __esm({
960
969
  metadata: ((_b = res["payload"]) == null ? void 0 : _b.metadata) || {}
961
970
  };
962
971
  });
972
+ if (results.length === 0) {
973
+ console.warn(`[QdrantProvider] \u26A0\uFE0F Retrieval returned 0 results. Check if data exists in namespace "${namespace}"`);
974
+ } else {
975
+ console.log(`[QdrantProvider] \u2705 Found ${results.length} results (best score: ${results[0].score.toFixed(4)})`);
976
+ }
977
+ return results;
963
978
  }
964
979
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
965
980
  async delete(id, _namespace) {
package/dist/server.mjs CHANGED
@@ -40,7 +40,7 @@ import {
40
40
  sseFrame,
41
41
  sseMetaFrame,
42
42
  sseTextFrame
43
- } from "./chunk-3GKA3PJF.mjs";
43
+ } from "./chunk-7S2SRQGL.mjs";
44
44
  import "./chunk-YLTMFW4M.mjs";
45
45
  import {
46
46
  PineconeProvider
@@ -56,7 +56,7 @@ import {
56
56
  } from "./chunk-U55XRW3U.mjs";
57
57
  import {
58
58
  QdrantProvider
59
- } from "./chunk-3DSHW676.mjs";
59
+ } from "./chunk-NT5VP7MT.mjs";
60
60
  import {
61
61
  BaseVectorProvider
62
62
  } from "./chunk-IMP6FUCY.mjs";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@retrivora-ai/rag-engine",
3
- "version": "1.0.7",
3
+ "version": "1.0.9",
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",
@@ -10,10 +10,16 @@ interface ChatWidgetProps {
10
10
  position?: 'bottom-right' | 'bottom-left';
11
11
  }
12
12
 
13
+ const DEFAULT_DIMENSIONS = { width: 380, height: 600 };
14
+
13
15
  export function ChatWidget({ position = 'bottom-right' }: ChatWidgetProps) {
14
16
  const { ui } = useConfig();
15
17
  const [isOpen, setIsOpen] = useState(false);
16
18
  const [hasUnread, setHasUnread] = useState(false);
19
+ const [dimensions, setDimensions] = useState(DEFAULT_DIMENSIONS);
20
+ const [isResizing, setIsResizing] = useState(false);
21
+ const [isMaximized, setIsMaximized] = useState(false);
22
+ const [prevDimensions, setPrevDimensions] = useState(DEFAULT_DIMENSIONS);
17
23
 
18
24
  if (ui.showWidget === false) return null;
19
25
 
@@ -33,20 +39,78 @@ export function ChatWidget({ position = 'bottom-right' }: ChatWidgetProps) {
33
39
  setHasUnread(false);
34
40
  };
35
41
 
42
+ const handleResizeStart = (e: React.MouseEvent) => {
43
+ e.preventDefault();
44
+ setIsResizing(true);
45
+ const startX = e.clientX;
46
+ const startY = e.clientY;
47
+ const startWidth = dimensions.width;
48
+ const startHeight = dimensions.height;
49
+
50
+ const onMouseMove = (moveEvent: MouseEvent) => {
51
+ const deltaX = startX - moveEvent.clientX;
52
+ const deltaY = startY - moveEvent.clientY;
53
+
54
+ setDimensions({
55
+ width: Math.max(320, Math.min(startWidth + deltaX, window.innerWidth - 48)),
56
+ height: Math.max(400, Math.min(startHeight + deltaY, window.innerHeight - 100)),
57
+ });
58
+ };
59
+
60
+ const onMouseUp = () => {
61
+ setIsResizing(false);
62
+ window.removeEventListener('mousemove', onMouseMove);
63
+ window.removeEventListener('mouseup', onMouseUp);
64
+ };
65
+
66
+ window.addEventListener('mousemove', onMouseMove);
67
+ window.addEventListener('mouseup', onMouseUp);
68
+ };
69
+
70
+ const handleResetResize = () => {
71
+ setDimensions(DEFAULT_DIMENSIONS);
72
+ setIsMaximized(false);
73
+ };
74
+
75
+ const handleMaximize = () => {
76
+ if (isMaximized) {
77
+ setDimensions(prevDimensions);
78
+ setIsMaximized(false);
79
+ } else {
80
+ setPrevDimensions(dimensions);
81
+ setDimensions({
82
+ width: Math.min(800, window.innerWidth - 48),
83
+ height: Math.min(800, window.innerHeight - 100),
84
+ });
85
+ setIsMaximized(true);
86
+ }
87
+ };
88
+
89
+ const isResized = dimensions.width !== DEFAULT_DIMENSIONS.width || dimensions.height !== DEFAULT_DIMENSIONS.height;
90
+
36
91
  return (
37
92
  <>
38
93
  {/* ── Chat Window (slides up) ── */}
39
94
  <div
40
- className={`fixed z-[9998] w-[380px] max-w-[calc(100vw-3rem)] transition-all duration-300 ease-in-out ${windowPositionClass} ${isOpen
95
+ className={`fixed z-[9998] max-w-[calc(100vw-3rem)] ease-in-out ${windowPositionClass} ${isOpen
41
96
  ? 'opacity-100 translate-y-0 pointer-events-auto'
42
97
  : 'opacity-0 translate-y-4 pointer-events-none'
43
- }`}
44
- style={{ height: '600px', maxHeight: 'calc(100vh - 6rem)' }}
98
+ } ${isResizing ? '' : 'transition-all duration-300'}`}
99
+ style={{
100
+ width: `${dimensions.width}px`,
101
+ height: `${dimensions.height}px`,
102
+ maxHeight: 'calc(100vh - 6rem)'
103
+ }}
45
104
  >
46
105
  <ChatWindow
47
106
  className="h-full relative z-10"
48
107
  showClose
49
108
  onClose={() => setIsOpen(false)}
109
+ onResizeStart={ui.allowResize !== false ? handleResizeStart : undefined}
110
+ onResetResize={ui.allowResize !== false ? handleResetResize : undefined}
111
+ isResized={isResized}
112
+ onMaximize={ui.allowResize !== false ? handleMaximize : undefined}
113
+ isMaximized={isMaximized}
50
114
  />
51
115
  {/* Pointer (Tail) */}
52
116
  <div
@@ -8,6 +8,9 @@ import {
8
8
  Sparkles,
9
9
  ArrowUp,
10
10
  TriangleAlert,
11
+ RotateCcw,
12
+ Maximize2,
13
+ Minimize2,
11
14
  } from 'lucide-react';
12
15
  import { MessageBubble } from './MessageBubble';
13
16
  import { useConfig } from './ConfigProvider';
@@ -23,9 +26,29 @@ interface ChatWindowProps {
23
26
  onClose?: () => void;
24
27
  /** Whether to show a close (X) button in the header */
25
28
  showClose?: boolean;
29
+ /** Called when the user starts dragging the resize handle */
30
+ onResizeStart?: (e: React.MouseEvent) => void;
31
+ /** Called when the user clicks the reset size button */
32
+ onResetResize?: () => void;
33
+ /** Whether the window has been resized from its default */
34
+ isResized?: boolean;
35
+ /** Called when the user clicks the maximize/minimize button */
36
+ onMaximize?: () => void;
37
+ /** Whether the window is currently maximized */
38
+ isMaximized?: boolean;
26
39
  }
27
40
 
28
- export function ChatWindow({ className = '', style, onClose, showClose = false }: ChatWindowProps) {
41
+ export function ChatWindow({
42
+ className = '',
43
+ style,
44
+ onClose,
45
+ showClose = false,
46
+ onResizeStart,
47
+ onResetResize,
48
+ isResized = false,
49
+ onMaximize,
50
+ isMaximized = false
51
+ }: ChatWindowProps) {
29
52
  const { ui, projectId } = useConfig();
30
53
  const [input, setInput] = useState('');
31
54
  const [mounted, setMounted] = useState(false);
@@ -73,13 +96,23 @@ export function ChatWindow({ className = '', style, onClose, showClose = false }
73
96
 
74
97
  return (
75
98
  <div
76
- className={`flex flex-col border border-slate-200 dark:border-white/10 shadow-2xl transition-all duration-300 ${currentRadius} ${
99
+ className={`relative flex flex-col border border-slate-200 dark:border-white/10 shadow-2xl transition-all duration-300 ${currentRadius} ${
77
100
  isGlass
78
101
  ? 'bg-white/90 dark:bg-[#0f0f1a]/90 backdrop-blur-xl'
79
102
  : 'bg-white dark:bg-[#0f0f1a]'
80
103
  } ${className}`}
81
104
  style={{ '--primary': ui.primaryColor, '--accent': ui.accentColor, ...style } as React.CSSProperties}
82
105
  >
106
+ {/* Resize Handle (Top-Left) */}
107
+ {onResizeStart && (
108
+ <div
109
+ onMouseDown={onResizeStart}
110
+ className="absolute top-0 left-0 w-6 h-6 cursor-nw-resize z-[100] group flex items-start justify-start p-1"
111
+ title="Drag to resize"
112
+ >
113
+ <div className="w-2.5 h-2.5 border-t-2 border-l-2 border-slate-300 dark:border-white/20 group-hover:border-slate-500 dark:group-hover:border-white/50 transition-colors rounded-tl-[2px]" />
114
+ </div>
115
+ )}
83
116
  {/* ── Header ── */}
84
117
  <div
85
118
  className="flex items-center justify-between px-5 py-4 border-b border-slate-200 dark:border-white/10"
@@ -105,32 +138,54 @@ export function ChatWindow({ className = '', style, onClose, showClose = false }
105
138
  </div>
106
139
  </div>
107
140
 
108
- <div className="flex items-center gap-2">
141
+ <div className="flex items-center gap-1.5">
109
142
  {/* Online indicator */}
110
- <span className="flex items-center gap-1 text-[10px] text-emerald-500 dark:text-emerald-400">
143
+ <span className="flex items-center gap-1 text-[10px] text-emerald-500 dark:text-emerald-400 mr-2">
111
144
  <span className="w-1.5 h-1.5 rounded-full bg-emerald-500 dark:bg-emerald-400 animate-pulse" />
112
145
  Online
113
146
  </span>
114
147
 
115
- {mounted && messages.length > 0 && (
116
- <button
117
- onClick={clearHistory}
118
- title="Clear conversation"
119
- className="w-7 h-7 rounded-lg flex items-center justify-center text-slate-400 hover:text-slate-600 dark:text-white/40 dark:hover:text-white/70 hover:bg-slate-100 dark:hover:bg-white/10 transition-all cursor-pointer"
120
- >
121
- <Trash2 className="w-3.5 h-3.5" />
122
- </button>
123
- )}
148
+ <div className="flex items-center bg-slate-100/50 dark:bg-white/5 rounded-lg p-0.5 border border-slate-200/50 dark:border-white/5">
149
+ {mounted && messages.length > 0 && (
150
+ <button
151
+ onClick={clearHistory}
152
+ title="Clear conversation"
153
+ className="w-7 h-7 rounded-md flex items-center justify-center text-slate-400 hover:text-rose-500 dark:text-white/40 dark:hover:text-rose-400 hover:bg-white dark:hover:bg-white/10 transition-all cursor-pointer"
154
+ >
155
+ <Trash2 className="w-3.5 h-3.5" />
156
+ </button>
157
+ )}
124
158
 
125
- {showClose && onClose && (
126
- <button
127
- onClick={onClose}
128
- title="Close chat"
129
- className="w-7 h-7 rounded-lg flex items-center justify-center text-slate-400 hover:text-slate-600 dark:text-white/40 dark:hover:text-white/70 hover:bg-slate-100 dark:hover:bg-white/10 transition-all cursor-pointer"
130
- >
131
- <X className="w-4 h-4" />
132
- </button>
133
- )}
159
+ {isResized && onResetResize && (
160
+ <button
161
+ onClick={onResetResize}
162
+ title="Reset to default size"
163
+ className="w-7 h-7 rounded-md flex items-center justify-center text-slate-400 hover:text-blue-500 dark:text-white/40 dark:hover:text-blue-400 hover:bg-white dark:hover:bg-white/10 transition-all cursor-pointer"
164
+ >
165
+ <RotateCcw className="w-3.5 h-3.5" />
166
+ </button>
167
+ )}
168
+
169
+ {onMaximize && (
170
+ <button
171
+ onClick={onMaximize}
172
+ title={isMaximized ? "Minimize" : "Maximize"}
173
+ className="w-7 h-7 rounded-md flex items-center justify-center text-slate-400 hover:text-blue-500 dark:text-white/40 dark:hover:text-blue-400 hover:bg-white dark:hover:bg-white/10 transition-all cursor-pointer"
174
+ >
175
+ {isMaximized ? <Minimize2 className="w-3.5 h-3.5" /> : <Maximize2 className="w-3.5 h-3.5" />}
176
+ </button>
177
+ )}
178
+
179
+ {showClose && onClose && (
180
+ <button
181
+ onClick={onClose}
182
+ title="Close chat"
183
+ className="w-7 h-7 rounded-md flex items-center justify-center text-slate-400 hover:text-slate-600 dark:text-white/40 dark:hover:text-white/70 hover:bg-white dark:hover:bg-white/10 transition-all cursor-pointer"
184
+ >
185
+ <X className="w-4 h-4" />
186
+ </button>
187
+ )}
188
+ </div>
134
189
  </div>
135
190
  </div>
136
191
 
@@ -32,6 +32,7 @@ const defaultConfig: ClientConfig = {
32
32
  visualStyle: 'glass',
33
33
  borderRadius: 'xl',
34
34
  allowUpload: true,
35
+ allowResize: true,
35
36
  },
36
37
  };
37
38
 
@@ -147,6 +147,8 @@ export interface UIConfig {
147
147
  borderRadius?: 'none' | 'sm' | 'md' | 'lg' | 'xl' | 'full';
148
148
  /** Whether to allow file uploads directly from the UI */
149
149
  allowUpload?: boolean;
150
+ /** Whether to allow manual resizing of the chat window. Defaults to true. */
151
+ allowResize?: boolean;
150
152
  }
151
153
 
152
154
  // ---------------------------------------------------------------------------
@@ -106,8 +106,21 @@ export class QdrantProvider extends BaseVectorProvider {
106
106
  await this.http.put(`/collections/${this.indexName}/points`, payload);
107
107
  }
108
108
 
109
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
110
109
  async query(vector: number[], topK: number, namespace?: string, _filter?: Record<string, unknown>): Promise<VectorMatch[]> {
110
+ const must: any[] = [];
111
+
112
+ if (namespace) {
113
+ must.push({ key: 'namespace', match: { value: namespace } });
114
+ }
115
+
116
+ // 1. Apply metadata filters
117
+ const sanitizedFilter = this.sanitizeFilter(_filter);
118
+ for (const [key, value] of Object.entries(sanitizedFilter)) {
119
+ // Qdrant uses dot notation for nested payload fields.
120
+ // Our payload structure is { content, metadata: { ... }, namespace }
121
+ must.push({ key: `metadata.${key}`, match: { value: value } });
122
+ }
123
+
111
124
  const payload = {
112
125
  vector: vector,
113
126
  limit: topK,
@@ -116,19 +129,33 @@ export class QdrantProvider extends BaseVectorProvider {
116
129
  hnsw_ef: (this.config.options.efSearch as number) || Math.max(topK * 20, 128),
117
130
  exact: false
118
131
  },
119
- filter: namespace ? {
120
- must: [{ key: 'namespace', match: { value: namespace } }]
121
- } : undefined,
132
+ filter: must.length > 0 ? { must } : undefined,
122
133
  };
134
+
135
+ console.log(`[QdrantProvider] 🔍 Searching "${this.indexName}" | Namespace: ${namespace || 'default'} | TopK: ${topK}`);
136
+ if (must.length > (namespace ? 1 : 0)) {
137
+ console.log(`[QdrantProvider] 🛠 Filters:`, JSON.stringify(must.filter(m => m.key !== 'namespace')));
138
+ }
139
+
123
140
  const { data } = await this.http.post(`/collections/${this.indexName}/points/search`, payload);
124
- return (data.result || []).map((res: Record<string, unknown>) => ({
141
+
142
+ const results = (data.result || []).map((res: Record<string, unknown>) => ({
125
143
  id: res['id'] as string,
126
144
  score: res['score'] as number,
127
145
  content: (res['payload'] as Record<string, unknown>)?.content as string || '',
128
146
  metadata: (res['payload'] as Record<string, unknown>)?.metadata as Record<string, unknown> || {},
129
147
  }));
148
+
149
+ if (results.length === 0) {
150
+ console.warn(`[QdrantProvider] ⚠️ Retrieval returned 0 results. Check if data exists in namespace "${namespace}"`);
151
+ } else {
152
+ console.log(`[QdrantProvider] ✅ Found ${results.length} results (best score: ${results[0].score.toFixed(4)})`);
153
+ }
154
+
155
+ return results;
130
156
  }
131
157
 
158
+
132
159
  // eslint-disable-next-line @typescript-eslint/no-unused-vars
133
160
  async delete(id: string | number, _namespace?: string): Promise<void> {
134
161
  await this.http.post(`/collections/${this.indexName}/points/delete`, {