Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
e270b53
pahse-1 doc parcing and storing in vector db
AshishViradiya153 Sep 4, 2025
4370d23
package update
AshishViradiya153 Sep 4, 2025
6f2cff8
featureflag for indexing
AshishViradiya153 Sep 4, 2025
e093d9e
indexing improvement
AshishViradiya153 Sep 5, 2025
88ac603
track indexing time
AshishViradiya153 Sep 6, 2025
91975ce
improvement
AshishViradiya153 Sep 6, 2025
aa210d5
overall improvement(token count and db update)
AshishViradiya153 Sep 8, 2025
542313c
prettier
AshishViradiya153 Sep 8, 2025
c07d715
phase2
AshishViradiya153 Sep 8, 2025
345f188
cleanup
AshishViradiya153 Sep 10, 2025
f92cff7
chore: cleanup
AshishViradiya153 Sep 10, 2025
044472f
service update
AshishViradiya153 Sep 10, 2025
7566ea1
util update
AshishViradiya153 Sep 10, 2025
a5752a0
remove session for now
AshishViradiya153 Sep 10, 2025
756d124
prompt update
AshishViradiya153 Sep 10, 2025
3713aef
cleanup
AshishViradiya153 Sep 10, 2025
9a30fd8
session and system improvement and cleanup
AshishViradiya153 Sep 13, 2025
953839b
improve chunking - token saver
AshishViradiya153 Sep 15, 2025
9b04f2e
handle polling fail
AshishViradiya153 Sep 15, 2025
28d267d
image description using local modal
AshishViradiya153 Sep 16, 2025
6725573
improved image description
AshishViradiya153 Sep 16, 2025
e5ba14f
UI: session chat message
AshishViradiya153 Sep 19, 2025
cd2dc76
refactor: use env vars for LLM model names
AshishViradiya153 Sep 19, 2025
6311328
fix: dont abort api on session change
AshishViradiya153 Sep 19, 2025
ef9a901
fix: fix console warning
AshishViradiya153 Sep 19, 2025
5c3db91
cleanup dead files
AshishViradiya153 Sep 22, 2025
e1e5313
schema update
AshishViradiya153 Sep 22, 2025
63d716e
improved versio of rag
AshishViradiya153 Sep 22, 2025
fd27d00
fix: loading session chats
AshishViradiya153 Sep 22, 2025
f8f8bee
fix: catch data bug
AshishViradiya153 Sep 23, 2025
41b9afb
chore: remove xenova/transformers
AshishViradiya153 Sep 23, 2025
7bd4ec8
chore: remove reranker using modal
AshishViradiya153 Sep 23, 2025
9a801bd
improvement and added dubug logs
AshishViradiya153 Sep 23, 2025
cf5cec4
tool improvement
AshishViradiya153 Sep 23, 2025
a3d297e
fix: cleanup text area
AshishViradiya153 Sep 24, 2025
92b4914
improvement: chat module
AshishViradiya153 Sep 24, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 33 additions & 3 deletions app/(ee)/api/links/[id]/upload/route.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
import { NextRequest, NextResponse } from "next/server";

import { processDocument } from "@/lib/api/documents/process-document";
import { verifyDataroomSession } from "@/lib/auth/dataroom-auth";
import { DocumentData } from "@/lib/documents/create-document";
import prisma from "@/lib/prisma";
import { processDocument } from "@/lib/api/documents/process-document";
import { supportsAdvancedExcelMode } from "@/lib/utils/get-content-type";
import prisma from "@/lib/prisma";
import { waitUntil } from "@vercel/functions";
import { triggerDataroomIndexing } from "@/lib/rag/indexing-trigger";
import { DocumentData } from "@/lib/documents/create-document";
import { getFeatureFlags } from "@/lib/featureFlags";

export async function POST(
request: NextRequest,
Expand Down Expand Up @@ -148,6 +151,33 @@ export async function POST(
},
});

try {
const features = await getFeatureFlags({ teamId: link.teamId });
if (features.ragIndexing) {
const indexingPromise = triggerDataroomIndexing(
dataroomId,
link.teamId,
viewerId
);
try {
waitUntil(indexingPromise);
} catch {
void indexingPromise.catch((e) =>
console.error(
`RAG indexing trigger (fallback) failed for dataroom ${dataroomId}.`,
e
)
);
}
}
} catch (ragError) {
console.error(
`RAG indexing trigger setup failed for dataroom ${dataroomId}. Error:`,
ragError
);
}


return NextResponse.json({ success: true });
} catch (error) {
console.error("Error uploading document:", error);
Expand Down
254 changes: 254 additions & 0 deletions app/api/rag/chat/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,254 @@
import { NextRequest } from 'next/server';
import { ragMiddleware } from '@/lib/rag/middleware';
import { ragOrchestratorService } from '@/lib/rag/services/rag-orchestrator.service';
import { UnifiedQueryAnalysisResult, unifiedQueryAnalysisService } from '@/lib/rag/services/unified-query-analysis.service';
import { chatStorageService } from '@/lib/rag/services/chat-storage.service';
import { ChatMetadataTracker } from '@/lib/rag/services/chat-metadata-tracker.service';
import { createUIMessageStream, createUIMessageStreamResponse } from 'ai';

const API_CONFIG = {
REQUEST_TIMEOUT_MS: 60000,
ANALYSIS_TIMEOUT_MS: 18000,
SESSION_HEADER_NAME: 'X-Session-ID',
} as const;

const FALLBACK_RESPONSE = "I'm sorry, but I could not find the answer to your question in the provided documents.";

async function createFallbackResponse(
message: string = FALLBACK_RESPONSE,
chatSessionId?: string,
metadataTracker?: ChatMetadataTracker
): Promise<Response> {
if (chatSessionId && metadataTracker) {
try {
await chatStorageService.addMessage({
sessionId: chatSessionId,
role: 'assistant',
content: message,
metadata: metadataTracker.getMetadata()
});
} catch (error) {
console.error('❌ Failed to store fallback message:', error);
}
}

const response = new Response(message, {
status: 200,
headers: {
'Content-Type': 'text/plain; charset=utf-8',
},
});
if (chatSessionId) {
response.headers.set(API_CONFIG.SESSION_HEADER_NAME, chatSessionId);
}
return response;
}

function isAborted(signal: AbortSignal): boolean {
return signal.aborted;
}

function createAbortResponse(message: string = 'Request aborted'): Response {
return new Response(message, { status: 499 });
}

export async function POST(req: NextRequest) {
let analysisController: AbortController | undefined;
let chatSessionId: string | undefined;
let metadataTracker: ChatMetadataTracker | undefined;

try {
const abortSignal = req.signal;

const { messages, dataroomId, viewerId, query, linkId, selectedFolderIds, selectedDocIds, folderDocIds, sessionId } = await Promise.race([
ragMiddleware.validateRequest(req),
new Promise<never>((_, reject) =>
setTimeout(() => reject(new Error(`${FALLBACK_RESPONSE} after ${API_CONFIG.REQUEST_TIMEOUT_MS}ms`)),
API_CONFIG.REQUEST_TIMEOUT_MS)
)
]);

try {
chatSessionId = await chatStorageService.getOrCreateSession({
dataroomId,
linkId,
viewerId,
title: query,
}, sessionId || undefined);

await chatStorageService.addMessage({
sessionId: chatSessionId,
role: 'user',
content: query,
});

metadataTracker = ChatMetadataTracker.create();

} catch (error) {
console.error(FALLBACK_RESPONSE, error);
}

if (isAborted(abortSignal)) {
console.log('🛑 Request aborted before analysis');
return createAbortResponse(FALLBACK_RESPONSE);
}

analysisController = new AbortController();

const combinedSignal = abortSignal || analysisController.signal;

let analysisResult: UnifiedQueryAnalysisResult;
try {
if (metadataTracker) {
metadataTracker.startQueryAnalysis();
}

analysisResult = await Promise.race([
unifiedQueryAnalysisService.analyzeQuery(query, viewerId, dataroomId, combinedSignal, metadataTracker),
new Promise<never>((_, reject) =>
setTimeout(() => reject(new Error(`Query analysis timeout after ${API_CONFIG.ANALYSIS_TIMEOUT_MS}ms`)),
API_CONFIG.ANALYSIS_TIMEOUT_MS)
)
]);
if (metadataTracker) {
metadataTracker.endQueryAnalysis();
metadataTracker.setQueryAnalysis({
queryType: analysisResult.queryClassification.type,
intent: analysisResult.queryClassification.intent,
complexityLevel: analysisResult.complexityAnalysis?.complexityLevel,
complexityScore: analysisResult.complexityAnalysis?.complexityScore
});

metadataTracker.setSearchStrategy({
strategy: analysisResult.searchStrategy?.strategy,
confidence: analysisResult.searchStrategy?.confidence,
reasoning: analysisResult.searchStrategy?.reasoning
});
}
} catch (error) {
if (isAborted(abortSignal) || (error instanceof Error && (error.name === 'AbortError' || error.message.includes('aborted')))) {
console.log('🛑 Query analysis aborted gracefully');
return createAbortResponse('Request aborted by user');
}

console.error('Query analysis failed:', error);
return await createFallbackResponse(FALLBACK_RESPONSE, chatSessionId, metadataTracker);
}

if (isAborted(abortSignal)) {
console.log('🛑 Request aborted before processing');
return createAbortResponse();
}
console.log('analysisResult', analysisResult)
// Handle chitchat/abusive queries
if (['abusive', 'chitchat'].includes(analysisResult.queryClassification.type)) {
const contextualResponse = analysisResult.queryClassification.response ||
"I'm here to help you with your documents! How can I assist you with questions about your uploaded files?";
const stream = createUIMessageStream({
execute: ({ writer }) => {
const messageId = crypto.randomUUID();
writer.write({
type: 'text-start',
id: messageId
});
writer.write({
type: 'text-delta',
delta: contextualResponse,
id: messageId
});
writer.write({
type: 'text-end',
id: messageId
});
}
});

return createUIMessageStreamResponse({ stream });
}
const sanitizedQuery = analysisResult.sanitization?.sanitizedQuery || query;
const mentionedPageNumbers = analysisResult.queryExtraction?.pageNumbers || [];

// Document Access Control & Permission Validation
const { indexedDocuments, accessError } = await ragMiddleware.getAccessibleIndexedDocuments(
dataroomId,
viewerId,
{ selectedDocIds, selectedFolderIds, folderDocIds }
);

if (accessError || !indexedDocuments || indexedDocuments.length === 0) {
return await createFallbackResponse(FALLBACK_RESPONSE, chatSessionId, metadataTracker);
}

const optimalStrategy = {
strategy: analysisResult.searchStrategy?.strategy || 'StandardVectorSearch',
confidence: analysisResult.searchStrategy?.confidence || 0.7
};

if (isAborted(abortSignal)) {
console.log('🛑 Request aborted before RAG processing');
return createAbortResponse();
}

try {
const result = await ragOrchestratorService.processQuery(
sanitizedQuery,
dataroomId,
indexedDocuments,
messages,
optimalStrategy.strategy,
analysisResult.queryClassification.intent,
analysisResult.complexityAnalysis,
{
pageNumbers: mentionedPageNumbers,
queryRewriting: analysisResult.queryRewriting
},
API_CONFIG.REQUEST_TIMEOUT_MS,
abortSignal,
chatSessionId,
metadataTracker
);

if (!result) {
return await createFallbackResponse(FALLBACK_RESPONSE, chatSessionId, metadataTracker);
}

if (chatSessionId && result instanceof Response) {
result.headers.set(API_CONFIG.SESSION_HEADER_NAME, chatSessionId);
}
return result;
} catch (error) {
if (isAborted(abortSignal) || (error instanceof Error && (error.name === 'AbortError' || error.message.includes('aborted')))) {
console.log('🛑 RAG processing aborted gracefully');
return createAbortResponse('Request aborted by user');
}

return await createFallbackResponse(FALLBACK_RESPONSE, chatSessionId, metadataTracker);
}

} catch (error: unknown) {
console.log('error', error)
const errorMessage = error instanceof Error ? error.message : 'Internal server error';

console.error('RAG chat route error:', {
error: errorMessage,
timestamp: new Date().toISOString()
});

if (chatSessionId && metadataTracker) {
metadataTracker.setError({
type: 'GeneralError',
message: errorMessage,
isRetryable: true
});
metadataTracker.endTotal();
}

return await createFallbackResponse(FALLBACK_RESPONSE, chatSessionId, metadataTracker);
} finally {
if (analysisController) {
analysisController.abort();
}
}
}


49 changes: 49 additions & 0 deletions app/api/rag/sessions/[sessionId]/messages/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { NextRequest, NextResponse } from 'next/server';
import { z } from 'zod';
import { chatStorageService } from '@/lib/rag/services/chat-storage.service';
import { SessionIdSchema, handleZodError, handleSessionNotFoundError, handleFetchError, validateLimit, extractAndValidateQueryParams } from '@/lib/rag/services/validation';

export async function GET(
req: NextRequest,
{ params }: { params: { sessionId: string } }
) {
try {
const sessionId = SessionIdSchema.parse(params.sessionId);
const validatedParams = extractAndValidateQueryParams(req);

const limit = validatedParams.limit || 20;
const limitError = validateLimit(limit, 100, 'messages');
if (limitError) return limitError;

const result = await chatStorageService.getSessionMessages({
sessionId,
dataroomId: validatedParams.dataroomId,
linkId: validatedParams.linkId,
viewerId: validatedParams.viewerId,
limit: validatedParams.limit,
cursor: validatedParams.cursor || undefined,
});

const response = {
sessionId: result.sessionId,
messages: result.messages,
pagination: {
hasNext: result.messages.length === limit,
nextCursor: result.messages.length === limit ? result.messages[0]?.id : null,
}
};

return NextResponse.json(response);

} catch (error) {
if (error instanceof z.ZodError) {
return handleZodError(error);
}

if (error instanceof Error && error.message.includes('Session not found')) {
return handleSessionNotFoundError(params.sessionId);
}

return handleFetchError(error, 'session-messages');
}
}
34 changes: 34 additions & 0 deletions app/api/rag/sessions/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { NextRequest, NextResponse } from 'next/server';
import { z } from 'zod';
import { chatStorageService } from '@/lib/rag/services/chat-storage.service';
import { extractAndValidateQueryParams, handleFetchError, handleZodError, validateLimit } from '@/lib/rag/services/validation';

export async function GET(req: NextRequest) {
try {
const validatedParams = extractAndValidateQueryParams(req);

const limit = validatedParams.limit || 20;
const limitError = validateLimit(limit, 100, 'sessions');
if (limitError) return limitError;

const result = await chatStorageService.getSessionsPaginated({
dataroomId: validatedParams.dataroomId,
viewerId: validatedParams.viewerId,
linkId: validatedParams.linkId,
limit: validatedParams.limit,
cursor: validatedParams.cursor || undefined,
});

return NextResponse.json({
sessions: result.sessions,
pagination: result.pagination,
});

} catch (error) {
if (error instanceof z.ZodError) {
return handleZodError(error);
}

return handleFetchError(error, 'sessions');
}
}
Loading