rag-lite-ts 2.2.0 → 2.3.1

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 (100) hide show
  1. package/README.md +88 -5
  2. package/dist/cjs/cli/indexer.js +73 -15
  3. package/dist/cjs/cli/search.js +77 -2
  4. package/dist/cjs/cli/ui-server.d.ts +5 -0
  5. package/dist/cjs/cli/ui-server.js +152 -0
  6. package/dist/cjs/cli.js +53 -7
  7. package/dist/cjs/core/abstract-generator.d.ts +97 -0
  8. package/dist/cjs/core/abstract-generator.js +222 -0
  9. package/dist/cjs/core/binary-index-format.js +53 -10
  10. package/dist/cjs/core/db.d.ts +56 -0
  11. package/dist/cjs/core/db.js +105 -0
  12. package/dist/cjs/core/generator-registry.d.ts +114 -0
  13. package/dist/cjs/core/generator-registry.js +280 -0
  14. package/dist/cjs/core/index.d.ts +4 -0
  15. package/dist/cjs/core/index.js +11 -0
  16. package/dist/cjs/core/ingestion.js +3 -0
  17. package/dist/cjs/core/knowledge-base-manager.d.ts +109 -0
  18. package/dist/cjs/core/knowledge-base-manager.js +256 -0
  19. package/dist/cjs/core/lazy-dependency-loader.d.ts +43 -0
  20. package/dist/cjs/core/lazy-dependency-loader.js +111 -2
  21. package/dist/cjs/core/prompt-templates.d.ts +138 -0
  22. package/dist/cjs/core/prompt-templates.js +225 -0
  23. package/dist/cjs/core/response-generator.d.ts +132 -0
  24. package/dist/cjs/core/response-generator.js +69 -0
  25. package/dist/cjs/core/search-pipeline.js +1 -1
  26. package/dist/cjs/core/search.d.ts +72 -1
  27. package/dist/cjs/core/search.js +80 -7
  28. package/dist/cjs/core/types.d.ts +1 -0
  29. package/dist/cjs/core/vector-index-messages.d.ts +52 -0
  30. package/dist/cjs/core/vector-index-messages.js +5 -0
  31. package/dist/cjs/core/vector-index-worker.d.ts +6 -0
  32. package/dist/cjs/core/vector-index-worker.js +314 -0
  33. package/dist/cjs/core/vector-index.d.ts +45 -10
  34. package/dist/cjs/core/vector-index.js +279 -218
  35. package/dist/cjs/factories/generator-factory.d.ts +88 -0
  36. package/dist/cjs/factories/generator-factory.js +151 -0
  37. package/dist/cjs/factories/index.d.ts +1 -0
  38. package/dist/cjs/factories/index.js +5 -0
  39. package/dist/cjs/factories/ingestion-factory.js +3 -7
  40. package/dist/cjs/factories/search-factory.js +11 -0
  41. package/dist/cjs/index-manager.d.ts +23 -3
  42. package/dist/cjs/index-manager.js +84 -15
  43. package/dist/cjs/index.d.ts +11 -1
  44. package/dist/cjs/index.js +19 -1
  45. package/dist/cjs/text/generators/causal-lm-generator.d.ts +65 -0
  46. package/dist/cjs/text/generators/causal-lm-generator.js +197 -0
  47. package/dist/cjs/text/generators/index.d.ts +10 -0
  48. package/dist/cjs/text/generators/index.js +10 -0
  49. package/dist/cjs/text/generators/instruct-generator.d.ts +62 -0
  50. package/dist/cjs/text/generators/instruct-generator.js +192 -0
  51. package/dist/esm/cli/indexer.js +73 -15
  52. package/dist/esm/cli/search.js +77 -2
  53. package/dist/esm/cli/ui-server.d.ts +5 -0
  54. package/dist/esm/cli/ui-server.js +152 -0
  55. package/dist/esm/cli.js +53 -7
  56. package/dist/esm/core/abstract-generator.d.ts +97 -0
  57. package/dist/esm/core/abstract-generator.js +222 -0
  58. package/dist/esm/core/binary-index-format.js +53 -10
  59. package/dist/esm/core/db.d.ts +56 -0
  60. package/dist/esm/core/db.js +105 -0
  61. package/dist/esm/core/generator-registry.d.ts +114 -0
  62. package/dist/esm/core/generator-registry.js +280 -0
  63. package/dist/esm/core/index.d.ts +4 -0
  64. package/dist/esm/core/index.js +11 -0
  65. package/dist/esm/core/ingestion.js +3 -0
  66. package/dist/esm/core/knowledge-base-manager.d.ts +109 -0
  67. package/dist/esm/core/knowledge-base-manager.js +256 -0
  68. package/dist/esm/core/lazy-dependency-loader.d.ts +43 -0
  69. package/dist/esm/core/lazy-dependency-loader.js +111 -2
  70. package/dist/esm/core/prompt-templates.d.ts +138 -0
  71. package/dist/esm/core/prompt-templates.js +225 -0
  72. package/dist/esm/core/response-generator.d.ts +132 -0
  73. package/dist/esm/core/response-generator.js +69 -0
  74. package/dist/esm/core/search-pipeline.js +1 -1
  75. package/dist/esm/core/search.d.ts +72 -1
  76. package/dist/esm/core/search.js +80 -7
  77. package/dist/esm/core/types.d.ts +1 -0
  78. package/dist/esm/core/vector-index-messages.d.ts +52 -0
  79. package/dist/esm/core/vector-index-messages.js +5 -0
  80. package/dist/esm/core/vector-index-worker.d.ts +6 -0
  81. package/dist/esm/core/vector-index-worker.js +314 -0
  82. package/dist/esm/core/vector-index.d.ts +45 -10
  83. package/dist/esm/core/vector-index.js +279 -218
  84. package/dist/esm/factories/generator-factory.d.ts +88 -0
  85. package/dist/esm/factories/generator-factory.js +151 -0
  86. package/dist/esm/factories/index.d.ts +1 -0
  87. package/dist/esm/factories/index.js +5 -0
  88. package/dist/esm/factories/ingestion-factory.js +3 -7
  89. package/dist/esm/factories/search-factory.js +11 -0
  90. package/dist/esm/index-manager.d.ts +23 -3
  91. package/dist/esm/index-manager.js +84 -15
  92. package/dist/esm/index.d.ts +11 -1
  93. package/dist/esm/index.js +19 -1
  94. package/dist/esm/text/generators/causal-lm-generator.d.ts +65 -0
  95. package/dist/esm/text/generators/causal-lm-generator.js +197 -0
  96. package/dist/esm/text/generators/index.d.ts +10 -0
  97. package/dist/esm/text/generators/index.js +10 -0
  98. package/dist/esm/text/generators/instruct-generator.d.ts +62 -0
  99. package/dist/esm/text/generators/instruct-generator.js +192 -0
  100. package/package.json +14 -7
package/README.md CHANGED
@@ -11,7 +11,7 @@
11
11
  [![TypeScript](https://img.shields.io/badge/TypeScript-5.0+-blue.svg?style=flat-square)](https://www.typescriptlang.org/)
12
12
  [![Node.js](https://img.shields.io/badge/Node.js-18+-green.svg?style=flat-square)](https://nodejs.org/)
13
13
 
14
- [Quick Start](#quick-start) • [Features](#features) • [Documentation](#documentation) • [Examples](#examples) • [MCP Integration](#mcp-server-integration)
14
+ [Quick Start](#quick-start) • [Features](#features) • [Documentation](#documentation) • [Examples](#examples) • [UI](#web-interface-ui) • [MCP Integration](#mcp-server-integration)
15
15
 
16
16
  </div>
17
17
 
@@ -27,7 +27,7 @@ raglite ingest ./docs/
27
27
  raglite search "your query here"
28
28
  ```
29
29
 
30
- **That's it.** No API keys, no cloud services, no configuration hell.
30
+ **That's it.** No API keys, no cloud services, no configuration hell. Prefer a UI? Run `raglite ui` for a visual interface with drag‑and‑drop ingestion and search.
31
31
 
32
32
  ### šŸŽ¬ See It In Action
33
33
 
@@ -101,6 +101,7 @@ See [CHANGELOG.md](CHANGELOG.md) for complete details.
101
101
  - [How It Works](#-how-it-works)
102
102
  - [Supported Models](#-supported-models)
103
103
  - [Documentation](#-documentation)
104
+ - [Web Interface (UI)](#-web-interface-ui)
104
105
  - [MCP Server Integration](#-mcp-server-integration)
105
106
  - [Development](#-development)
106
107
  - [Contributing](#-contributing)
@@ -114,7 +115,7 @@ See [CHANGELOG.md](CHANGELOG.md) for complete details.
114
115
  npm install -g rag-lite-ts
115
116
  ```
116
117
 
117
- ### Basic Usage
118
+ ### Basic Usage (CLI)
118
119
 
119
120
  ```bash
120
121
  # Ingest documents
@@ -127,11 +128,32 @@ raglite search "machine learning concepts"
127
128
  raglite search "API documentation" --top-k 10 --rerank
128
129
  ```
129
130
 
131
+ ### Web Interface (UI)
132
+
133
+ Launch the visual web interface for interactive document management:
134
+
135
+ ```bash
136
+ # Start the UI (opens in browser)
137
+ raglite ui
138
+
139
+ # UI provides:
140
+ # - Drag & drop file upload
141
+ # - Real-time ingestion progress
142
+ # - Visual search interface
143
+ # - Image search with upload
144
+ # - Knowledge base statistics
145
+ ```
146
+
147
+ → **[Complete UI Guide](docs/ui-guide.md)**
148
+
130
149
  ### Using Different Models
131
150
 
132
151
  ```bash
133
- # Use higher quality model (auto-rebuilds if needed)
134
- raglite ingest ./docs/ --model Xenova/all-mpnet-base-v2 --rebuild-if-needed
152
+ # Use higher quality model
153
+ raglite ingest ./docs/ --model Xenova/all-mpnet-base-v2
154
+
155
+ # Switch models or refresh your data (DESTRUCTIVE: wipes DB+index and rebuilds from scratch)
156
+ raglite ingest ./docs/ --model Xenova/all-mpnet-base-v2 --force-rebuild
135
157
 
136
158
  # Search automatically uses the correct model
137
159
  raglite search "complex query"
@@ -441,6 +463,27 @@ Now Claude can search your docs directly! Works with any MCP-compatible AI tool.
441
463
  - **Model compatibility** - Auto-detection, rebuilds
442
464
  - **Error recovery** - Clear messages, helpful hints
443
465
 
466
+ </td>
467
+ </tr>
468
+ <tr>
469
+ <td width="50%">
470
+
471
+ ### šŸŽØ Web Interface (UI)
472
+ - **Visual ingestion** - Drag & drop file upload
473
+ - **Real-time progress** - Live ingestion tracking
474
+ - **Image search** - Upload images to find similar content
475
+ - **Interactive search** - Visual results with previews
476
+ - **Knowledge base stats** - Overview of your data
477
+
478
+ </td>
479
+ <td width="50%">
480
+
481
+ ### šŸ“š Documentation
482
+ - **Comprehensive guides** - CLI, API, multimodal tutorials
483
+ - **Quick start guides** - Get running in minutes
484
+ - **Troubleshooting** - Solutions for common issues
485
+ - **Examples** - Real-world use cases
486
+
444
487
  </td>
445
488
  </tr>
446
489
  </table>
@@ -579,6 +622,7 @@ Choose the right model for your use case:
579
622
 
580
623
  ### šŸš€ Getting Started
581
624
  - [CLI Reference](docs/cli-reference.md)
625
+ - [UI Guide](docs/ui-guide.md) šŸ†•
582
626
  - [API Reference](docs/api-reference.md)
583
627
  - [Multimodal Tutorial](docs/multimodal-tutorial.md)
584
628
  - [Unified Content System](docs/unified-content-system.md)
@@ -610,6 +654,7 @@ Choose the right model for your use case:
610
654
  | I want to... | Start here |
611
655
  |--------------|------------|
612
656
  | šŸ†• Try it out | [CLI Reference](docs/cli-reference.md) → `npm i -g rag-lite-ts` |
657
+ | šŸŽØ Use visual interface | [UI Guide](docs/ui-guide.md) → `raglite ui` |
613
658
  | šŸ–¼ļø Search images | [Multimodal Tutorial](docs/multimodal-tutorial.md) → `--mode multimodal` |
614
659
  | šŸ’» Build an app | [API Reference](docs/api-reference.md) → `new SearchEngine()` |
615
660
  | šŸ¤– Integrate with AI | [MCP Guide](docs/mcp-server-multimodal-guide.md) → `raglite-mcp` |
@@ -618,6 +663,44 @@ Choose the right model for your use case:
618
663
 
619
664
  **šŸ“– [Complete Documentation Hub](docs/README.md)**
620
665
 
666
+ ## šŸŽØ Web Interface (UI)
667
+
668
+ RAG-lite TS includes a modern web-based interface for visual document management and search.
669
+
670
+ ### Quick Start
671
+
672
+ ```bash
673
+ # Launch the UI (starts both frontend and backend)
674
+ raglite ui
675
+
676
+ # Opens in browser at http://localhost:3000
677
+ ```
678
+
679
+ ### Features
680
+
681
+ - **šŸ“¤ Visual Ingestion**: Drag & drop file upload with real-time progress
682
+ - **šŸ” Interactive Search**: Text and image search with visual results
683
+ - **šŸ“Š Knowledge Base Stats**: Overview of documents, chunks, and model information
684
+ - **āš™ļø Configuration**: Visual interface for all ingestion options
685
+ - **šŸ–¼ļø Image Search**: Upload images to find similar content
686
+ - **šŸ“ Folder Support**: Upload entire directory structures
687
+
688
+ ### UI vs CLI
689
+
690
+ | Feature | UI | CLI |
691
+ |---------|----|----|
692
+ | File upload | āœ… Drag & drop | āœ… Command-line |
693
+ | Progress tracking | āœ… Visual bars | āœ… Console output |
694
+ | Image search | āœ… Upload interface | āœ… File path |
695
+ | Configuration | āœ… Visual options | āœ… Command flags |
696
+ | Batch processing | āœ… | āœ… |
697
+ | Scripting | āŒ | āœ… |
698
+
699
+ **Use UI for:** Interactive exploration, visual feedback, learning the system
700
+ **Use CLI for:** Automation, scripting, headless environments
701
+
702
+ → **[Complete UI Guide](docs/ui-guide.md)**
703
+
621
704
  ## šŸ”Œ MCP Server Integration
622
705
 
623
706
  **Give your AI agents semantic memory.** RAG-lite TS includes a built-in Model Context Protocol (MCP) server.
@@ -80,7 +80,7 @@ async function validateModeConfiguration(options) {
80
80
  */
81
81
  export async function runIngest(path, options = {}) {
82
82
  try {
83
- // Handle --rebuild-if-needed flag immediately to prevent dimension mismatch error
83
+ // Handle --force-rebuild flag immediately to prevent dimension mismatch errors
84
84
  // Validate path exists
85
85
  const resolvedPath = resolve(path);
86
86
  if (!existsSync(resolvedPath)) {
@@ -159,26 +159,52 @@ export async function runIngest(path, options = {}) {
159
159
  factoryOptions.mode = options.mode;
160
160
  console.log(`Using processing mode: ${options.mode}`);
161
161
  }
162
- if (options.rebuildIfNeeded) {
162
+ if (options.forceRebuild) {
163
163
  factoryOptions.forceRebuild = true;
164
- console.log('Force rebuild enabled due to rebuildIfNeeded option');
165
- // Delete old index file immediately to prevent dimension mismatch errors
166
- const indexPath = process.env.RAG_INDEX_FILE || './vector-index.bin';
167
- const { existsSync, unlinkSync } = await import('fs');
168
- if (existsSync(indexPath)) {
169
- try {
170
- unlinkSync(indexPath);
171
- console.log('šŸ—‘ļø Removed old index file to prevent dimension mismatch');
172
- }
173
- catch (error) {
174
- console.warn(`āš ļø Could not remove old index file: ${error}`);
175
- }
176
- }
164
+ console.log('Force rebuild enabled (--force-rebuild)');
177
165
  }
178
166
  // Validate mode-specific model and strategy combinations
179
167
  await validateModeConfiguration(factoryOptions);
180
168
  const dbPath = process.env.RAG_DB_FILE || './db.sqlite';
181
169
  const indexPath = process.env.RAG_INDEX_FILE || './vector-index.bin';
170
+ // --force-rebuild: Always delete DB (and sidecars) and index to guarantee a clean rebuild.
171
+ if (options.forceRebuild) {
172
+ try {
173
+ const { existsSync: fsExistsSync, unlinkSync } = await import('fs');
174
+ console.log('šŸ—‘ļø Deleting existing database and index to perform a clean rebuild...');
175
+ // Remove WAL/SHM if present (common on SQLite with WAL journaling).
176
+ const sidecars = [`${dbPath}-wal`, `${dbPath}-shm`];
177
+ for (const p of sidecars) {
178
+ if (fsExistsSync(p)) {
179
+ try {
180
+ unlinkSync(p);
181
+ }
182
+ catch (e) {
183
+ console.warn(`āš ļø Could not remove SQLite sidecar file (${p}):`, e);
184
+ }
185
+ }
186
+ }
187
+ if (fsExistsSync(dbPath)) {
188
+ try {
189
+ unlinkSync(dbPath);
190
+ }
191
+ catch (e) {
192
+ console.warn(`āš ļø Could not remove database file (${dbPath}):`, e);
193
+ }
194
+ }
195
+ if (fsExistsSync(indexPath)) {
196
+ try {
197
+ unlinkSync(indexPath);
198
+ }
199
+ catch (e) {
200
+ console.warn(`āš ļø Could not remove index file (${indexPath}):`, e);
201
+ }
202
+ }
203
+ }
204
+ catch (error) {
205
+ console.warn('āš ļø Could not delete existing database/index for clean rebuild:', error instanceof Error ? error.message : String(error));
206
+ }
207
+ }
182
208
  // Setup graceful cleanup
183
209
  setupCLICleanup(dbPath);
184
210
  // Check if database is busy before starting
@@ -218,6 +244,22 @@ export async function runIngest(path, options = {}) {
218
244
  console.log(`Processing rate: ${chunksPerSecond} chunks/second`);
219
245
  }
220
246
  console.log('\nIngestion completed successfully!');
247
+ // Run VACUUM to compact the SQLite database after ingestion
248
+ try {
249
+ const { openDatabase } = await import('../core/db.js');
250
+ const vacuumDb = await openDatabase(dbPath);
251
+ try {
252
+ console.log('Running VACUUM to optimize database size...');
253
+ await vacuumDb.run('VACUUM');
254
+ console.log('VACUUM completed successfully.');
255
+ }
256
+ finally {
257
+ await vacuumDb.close();
258
+ }
259
+ }
260
+ catch (vacuumError) {
261
+ console.warn('āš ļø VACUUM operation failed or was skipped:', vacuumError instanceof Error ? vacuumError.message : String(vacuumError));
262
+ }
221
263
  // Display mode-specific information
222
264
  const mode = options.mode || 'text';
223
265
  if (mode === 'multimodal') {
@@ -397,6 +439,22 @@ export async function runRebuild() {
397
439
  console.log('All embeddings have been regenerated with the current model.');
398
440
  console.log('');
399
441
  console.log('You can now search your documents using: raglite search "your query"');
442
+ // Run VACUUM to compact the SQLite database after rebuild
443
+ try {
444
+ const { openDatabase } = await import('../core/db.js');
445
+ const vacuumDb = await openDatabase(dbPath);
446
+ try {
447
+ console.log('Running VACUUM to optimize database size after rebuild...');
448
+ await vacuumDb.run('VACUUM');
449
+ console.log('VACUUM completed successfully.');
450
+ }
451
+ finally {
452
+ await vacuumDb.close();
453
+ }
454
+ }
455
+ catch (vacuumError) {
456
+ console.warn('āš ļø VACUUM operation failed or was skipped:', vacuumError instanceof Error ? vacuumError.message : String(vacuumError));
457
+ }
400
458
  }
401
459
  finally {
402
460
  await db.close();
@@ -132,7 +132,7 @@ export async function runSearch(query, options = {}) {
132
132
  process.exit(EXIT_CODES.MODEL_ERROR);
133
133
  }
134
134
  }
135
- // Prepare search options
135
+ // Prepare search options (with generation support)
136
136
  const searchOptions = {};
137
137
  if (options['top-k'] !== undefined) {
138
138
  searchOptions.top_k = options['top-k'];
@@ -164,9 +164,60 @@ export async function runSearch(query, options = {}) {
164
164
  }
165
165
  // Track whether reranking will actually be used in this search
166
166
  const rerankingUsed = searchOptions.rerank === true;
167
+ // Handle generation options (experimental, text mode only)
168
+ const generateResponse = options.generate === true;
169
+ const generatorModel = options.generator;
170
+ const maxGenerationTokens = options['max-tokens'];
171
+ const generationTemperature = options.temperature;
172
+ const maxChunksForContext = options['max-chunks'];
173
+ // Generation only supported in text mode
174
+ if (generateResponse && isImage) {
175
+ console.warn('āš ļø [EXPERIMENTAL] Generation is only supported for text searches.');
176
+ console.warn(' Image search results will be returned without generation.');
177
+ console.warn('');
178
+ }
179
+ // Generation requires reranking - enable it automatically
180
+ let rerankingEnabledForGeneration = false;
181
+ if (generateResponse && !isImage && !searchOptions.rerank) {
182
+ searchOptions.rerank = true;
183
+ rerankingEnabledForGeneration = true;
184
+ console.log('šŸ“‹ Reranking automatically enabled (required for generation)');
185
+ }
186
+ // Set up generator if generation is requested (text mode only)
187
+ let generateFn;
188
+ if (generateResponse && !isImage) {
189
+ try {
190
+ console.log('šŸ¤– [EXPERIMENTAL] Initializing response generator...');
191
+ const { createGenerateFunctionFromModel, getDefaultGeneratorModel } = await import('../factories/generator-factory.js');
192
+ const { getDefaultMaxChunksForContext } = await import('../core/generator-registry.js');
193
+ const modelToUse = generatorModel || getDefaultGeneratorModel();
194
+ const defaultChunks = getDefaultMaxChunksForContext(modelToUse) || 3;
195
+ console.log(` Model: ${modelToUse}`);
196
+ console.log(` Max chunks for context: ${maxChunksForContext || defaultChunks} (default: ${defaultChunks})`);
197
+ generateFn = await createGenerateFunctionFromModel(modelToUse);
198
+ searchEngine.setGenerateFunction(generateFn);
199
+ console.log('āœ… Generator initialized');
200
+ console.log('');
201
+ }
202
+ catch (error) {
203
+ console.error('āŒ [EXPERIMENTAL] Failed to initialize generator:', error instanceof Error ? error.message : 'Unknown error');
204
+ console.error(' Continuing without generation...');
205
+ console.error('');
206
+ }
207
+ }
208
+ // Set generation options if generator is ready
209
+ if (generateFn && generateResponse && !isImage) {
210
+ searchOptions.generateResponse = true;
211
+ searchOptions.generationOptions = {
212
+ maxTokens: maxGenerationTokens,
213
+ temperature: generationTemperature,
214
+ maxChunksForContext: maxChunksForContext
215
+ };
216
+ }
167
217
  // Perform search
168
218
  const startTime = Date.now();
169
219
  let results;
220
+ let generationResult;
170
221
  if (isImage && embedder) {
171
222
  // Image-based search: embed the image and search with the vector
172
223
  console.log('Embedding image...');
@@ -174,8 +225,14 @@ export async function runSearch(query, options = {}) {
174
225
  console.log('Searching with image embedding...');
175
226
  results = await searchEngine.searchWithVector(imageEmbedding.vector, searchOptions);
176
227
  }
228
+ else if (generateResponse && generateFn) {
229
+ // Text-based search with generation
230
+ const searchResult = await searchEngine.searchWithGeneration(query, searchOptions);
231
+ results = searchResult.results;
232
+ generationResult = searchResult.generation;
233
+ }
177
234
  else {
178
- // Text-based search
235
+ // Standard text-based search
179
236
  results = await searchEngine.search(query, searchOptions);
180
237
  }
181
238
  const searchTime = Date.now() - startTime;
@@ -216,6 +273,21 @@ export async function runSearch(query, options = {}) {
216
273
  }
217
274
  console.log('');
218
275
  });
276
+ // Display generated response if available (experimental)
277
+ if (generationResult) {
278
+ console.log('─'.repeat(50));
279
+ console.log('šŸ¤– Generated Response [EXPERIMENTAL]');
280
+ console.log(`Model: ${generationResult.modelUsed}`);
281
+ console.log('─'.repeat(50));
282
+ console.log('');
283
+ console.log(generationResult.response);
284
+ console.log('');
285
+ console.log('─'.repeat(50));
286
+ console.log(`ā±ļø Generation: ${(generationResult.generationTimeMs / 1000).toFixed(1)}s | ` +
287
+ `šŸ“Š ${generationResult.tokensUsed} tokens | ` +
288
+ `šŸ“„ ${generationResult.chunksUsedForContext} chunks used` +
289
+ (generationResult.truncated ? ' (context truncated)' : ''));
290
+ }
219
291
  // Show search statistics
220
292
  const stats = await searchEngine.getStats();
221
293
  console.log('─'.repeat(50));
@@ -233,6 +305,9 @@ export async function runSearch(query, options = {}) {
233
305
  else {
234
306
  console.log('Reranking: disabled');
235
307
  }
308
+ if (generationResult) {
309
+ console.log('Generation: enabled [EXPERIMENTAL]');
310
+ }
236
311
  }
237
312
  }
238
313
  finally {
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Launch the UI server
3
+ */
4
+ export declare function runUI(options?: any): Promise<void>;
5
+ //# sourceMappingURL=ui-server.d.ts.map
@@ -0,0 +1,152 @@
1
+ import { fileURLToPath } from 'url';
2
+ import { dirname, join } from 'path';
3
+ import { spawn } from 'child_process';
4
+ import fs from 'fs';
5
+ const __filename = fileURLToPath(import.meta.url);
6
+ const __dirname = dirname(__filename);
7
+ /**
8
+ * Get the project root directory
9
+ * When built, CLI is at dist/esm/cli/ui-server.js, so go up 3 levels
10
+ * When running from source, CLI is at src/cli/ui-server.ts, so go up 2 levels
11
+ */
12
+ function getProjectRoot() {
13
+ // Try going up 3 levels first (for built version)
14
+ const builtPath = join(__dirname, '../../..');
15
+ if (fs.existsSync(join(builtPath, 'package.json'))) {
16
+ return builtPath;
17
+ }
18
+ // Fallback: go up 2 levels (for source execution)
19
+ return join(__dirname, '../..');
20
+ }
21
+ /**
22
+ * Launch the UI server
23
+ */
24
+ export async function runUI(options = {}) {
25
+ const port = options.port || 3000;
26
+ const backendPort = options.backendPort || 3001;
27
+ console.log('šŸš€ Launching RAG-lite TS UI...');
28
+ // Resolve UI paths from project root
29
+ const projectRoot = getProjectRoot();
30
+ const backendBuiltPath = join(projectRoot, 'ui', 'backend', 'dist', 'index.js');
31
+ const backendSourcePath = join(projectRoot, 'ui', 'backend', 'src', 'index.ts');
32
+ const frontendBuiltPath = join(projectRoot, 'ui', 'frontend', 'dist');
33
+ const frontendSourcePath = join(projectRoot, 'ui', 'frontend');
34
+ // Check if built files exist
35
+ const useBuiltBackend = fs.existsSync(backendBuiltPath);
36
+ const useBuiltFrontend = fs.existsSync(frontendBuiltPath);
37
+ if (!useBuiltBackend && !fs.existsSync(backendSourcePath)) {
38
+ console.error(`āŒ UI backend not found at: ${backendSourcePath}`);
39
+ console.error(' Make sure the UI is set up in the ui/ directory.');
40
+ process.exit(1);
41
+ }
42
+ if (!useBuiltFrontend && !fs.existsSync(frontendSourcePath)) {
43
+ console.error(`āŒ UI frontend not found at: ${frontendSourcePath}`);
44
+ console.error(' Make sure the UI is set up in the ui/ directory.');
45
+ process.exit(1);
46
+ }
47
+ // Pass the working directory where 'raglite ui' was called to the backend
48
+ const workingDir = process.cwd();
49
+ // Built mode: single server on port (UI + API). Dev mode: backend on backendPort, frontend on port.
50
+ const effectiveBackendPort = useBuiltFrontend ? port : backendPort;
51
+ console.log(`šŸ“” Starting backend on port ${effectiveBackendPort}...`);
52
+ // Start backend server - use built version if available
53
+ const backendCommand = useBuiltBackend ? 'node' : 'npx';
54
+ const backendArgs = useBuiltBackend
55
+ ? [backendBuiltPath]
56
+ : ['tsx', backendSourcePath];
57
+ const backendProcess = spawn(backendCommand, backendArgs, {
58
+ stdio: 'pipe',
59
+ env: {
60
+ ...process.env,
61
+ PORT: effectiveBackendPort.toString(),
62
+ RAG_WORKING_DIR: workingDir,
63
+ UI_FRONTEND_DIST: useBuiltFrontend ? frontendBuiltPath : undefined
64
+ },
65
+ shell: true
66
+ });
67
+ backendProcess.on('error', (err) => {
68
+ console.error('āŒ Failed to start backend process:', err);
69
+ process.exit(1);
70
+ });
71
+ // Forward backend output with prefix
72
+ backendProcess.stdout?.on('data', (data) => {
73
+ process.stdout.write(`[Backend] ${data}`);
74
+ });
75
+ backendProcess.stderr?.on('data', (data) => {
76
+ process.stderr.write(`[Backend] ${data}`);
77
+ });
78
+ // Only start frontend dev server if built version doesn't exist
79
+ let frontendProcess = null;
80
+ if (!useBuiltFrontend) {
81
+ console.log(`šŸŽØ Starting frontend dev server on port ${port}...`);
82
+ frontendProcess = spawn('npm', ['run', 'dev'], {
83
+ cwd: frontendSourcePath,
84
+ stdio: 'pipe',
85
+ env: {
86
+ ...process.env,
87
+ VITE_API_URL: `http://localhost:${effectiveBackendPort}`
88
+ },
89
+ shell: true
90
+ });
91
+ frontendProcess.on('error', (err) => {
92
+ console.error('āŒ Failed to start frontend process:', err);
93
+ backendProcess.kill();
94
+ process.exit(1);
95
+ });
96
+ // Forward frontend output with prefix
97
+ frontendProcess.stdout?.on('data', (data) => {
98
+ process.stdout.write(`[Frontend] ${data}`);
99
+ });
100
+ frontendProcess.stderr?.on('data', (data) => {
101
+ process.stderr.write(`[Frontend] ${data}`);
102
+ });
103
+ }
104
+ else {
105
+ console.log(`šŸŽØ Using built frontend from ${frontendBuiltPath}`);
106
+ console.log(` Frontend will be served by backend on port ${effectiveBackendPort}`);
107
+ }
108
+ // Wait a bit for servers to start
109
+ await new Promise(resolve => setTimeout(resolve, 2000));
110
+ console.log(`\n✨ UI Access:`);
111
+ if (useBuiltFrontend) {
112
+ console.log(` Frontend & Backend: http://localhost:${port}`);
113
+ }
114
+ else {
115
+ console.log(` Frontend: http://localhost:${port}`);
116
+ console.log(` Backend: http://localhost:${effectiveBackendPort}`);
117
+ }
118
+ console.log(`\nšŸ’” Press Ctrl+C to stop both servers\n`);
119
+ // Keep the process alive and handle cleanup
120
+ return new Promise((resolve) => {
121
+ const cleanup = () => {
122
+ console.log('\nšŸ›‘ Shutting down servers...');
123
+ backendProcess.kill();
124
+ if (frontendProcess) {
125
+ frontendProcess.kill();
126
+ }
127
+ resolve();
128
+ };
129
+ process.on('SIGINT', cleanup);
130
+ process.on('SIGTERM', cleanup);
131
+ // Handle process exits
132
+ backendProcess.on('exit', (code) => {
133
+ if (code !== 0 && code !== null) {
134
+ console.error(`\nāŒ Backend process exited with code ${code}`);
135
+ if (frontendProcess) {
136
+ frontendProcess.kill();
137
+ }
138
+ resolve();
139
+ }
140
+ });
141
+ if (frontendProcess) {
142
+ frontendProcess.on('exit', (code) => {
143
+ if (code !== 0 && code !== null) {
144
+ console.error(`\nāŒ Frontend process exited with code ${code}`);
145
+ backendProcess.kill();
146
+ resolve();
147
+ }
148
+ });
149
+ }
150
+ });
151
+ }
152
+ //# sourceMappingURL=ui-server.js.map