Add clickable link for vector search results
Browse files
client/src/components/knowledge-base/result-card.tsx
CHANGED
@@ -285,7 +285,17 @@ export default function ResultCard({
|
|
285 |
</Badge>
|
286 |
</div>
|
287 |
<h3 className="text-lg font-semibold text-slate-900 mb-2 line-clamp-2">
|
288 |
-
{document.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
289 |
</h3>
|
290 |
<p className="text-sm text-slate-600 mb-3">
|
291 |
{document.source}
|
|
|
285 |
</Badge>
|
286 |
</div>
|
287 |
<h3 className="text-lg font-semibold text-slate-900 mb-2 line-clamp-2">
|
288 |
+
{document.url ? (
|
289 |
+
<button
|
290 |
+
onClick={handleViewSource}
|
291 |
+
className="text-left hover:text-blue-600 dark:hover:text-blue-400 transition-colors duration-200 hover:underline cursor-pointer w-full"
|
292 |
+
title="Click to view source"
|
293 |
+
>
|
294 |
+
{document.title}
|
295 |
+
</button>
|
296 |
+
) : (
|
297 |
+
<span>{document.title}</span>
|
298 |
+
)}
|
299 |
</h3>
|
300 |
<p className="text-sm text-slate-600 mb-3">
|
301 |
{document.source}
|
server/document-processor.ts
CHANGED
@@ -3,6 +3,7 @@ import path from 'path';
|
|
3 |
import { modalClient } from './modal-client';
|
4 |
import { nebiusClient } from './nebius-client';
|
5 |
import { FileProcessor } from './file-upload';
|
|
|
6 |
import { type Document, type InsertDocument } from '@shared/schema';
|
7 |
|
8 |
export interface ProcessingResult {
|
@@ -440,11 +441,45 @@ export class DocumentProcessor {
|
|
440 |
}> {
|
441 |
try {
|
442 |
const result = await modalClient.vectorSearch(query, indexName, maxResults);
|
443 |
-
|
444 |
if (result.status === 'completed') {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
445 |
return {
|
446 |
success: true,
|
447 |
-
results:
|
448 |
};
|
449 |
} else {
|
450 |
return {
|
|
|
3 |
import { modalClient } from './modal-client';
|
4 |
import { nebiusClient } from './nebius-client';
|
5 |
import { FileProcessor } from './file-upload';
|
6 |
+
import { storage } from './storage';
|
7 |
import { type Document, type InsertDocument } from '@shared/schema';
|
8 |
|
9 |
export interface ProcessingResult {
|
|
|
441 |
}> {
|
442 |
try {
|
443 |
const result = await modalClient.vectorSearch(query, indexName, maxResults);
|
|
|
444 |
if (result.status === 'completed') {
|
445 |
+
// Enrich vector search results with complete document data from database
|
446 |
+
const enrichedResults = await Promise.all(
|
447 |
+
result.results.map(async (vectorResult: any) => {
|
448 |
+
try {
|
449 |
+
// Get complete document data from database using the ID
|
450 |
+
const dbDocument = await storage.getDocument(parseInt(vectorResult.id));
|
451 |
+
if (dbDocument) {
|
452 |
+
// Merge vector search metadata with database document
|
453 |
+
// Ensure the URL field is preserved from the database
|
454 |
+
const enriched = {
|
455 |
+
id: dbDocument.id,
|
456 |
+
title: dbDocument.title,
|
457 |
+
content: dbDocument.content,
|
458 |
+
source: dbDocument.source,
|
459 |
+
sourceType: dbDocument.sourceType,
|
460 |
+
url: dbDocument.url, // Explicitly preserve URL
|
461 |
+
metadata: dbDocument.metadata,
|
462 |
+
createdAt: dbDocument.createdAt,
|
463 |
+
// Add vector search specific fields
|
464 |
+
relevanceScore: vectorResult.relevanceScore,
|
465 |
+
rank: vectorResult.rank,
|
466 |
+
snippet: vectorResult.snippet || dbDocument.content.substring(0, 200) + '...'
|
467 |
+
};
|
468 |
+
return enriched;
|
469 |
+
} else {
|
470 |
+
// Fallback to vector result if database document not found
|
471 |
+
return vectorResult;
|
472 |
+
}
|
473 |
+
} catch (error) {
|
474 |
+
console.warn(`Failed to enrich vector result for ID ${vectorResult.id}:`, error);
|
475 |
+
return vectorResult;
|
476 |
+
}
|
477 |
+
})
|
478 |
+
);
|
479 |
+
|
480 |
return {
|
481 |
success: true,
|
482 |
+
results: enrichedResults
|
483 |
};
|
484 |
} else {
|
485 |
return {
|