Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
86 changes: 86 additions & 0 deletions app/api/collections/[collection_id]/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import { NextResponse } from 'next/server';

const backendUrl = process.env.NEXT_PUBLIC_BACKEND_URL || 'http://localhost:8000';

// GET /api/collections/[collection_id] - Get a specific collection
export async function GET(
request: Request,
{ params }: { params: Promise<{ collection_id: string }> }
) {
const { collection_id } = await params;
const apiKey = request.headers.get('X-API-KEY');

if (!apiKey) {
return NextResponse.json(
{ error: 'Missing X-API-KEY header' },
{ status: 401 }
);
}

try {
const response = await fetch(
`${backendUrl}/api/v1/collections/${collection_id}?include_docs=true`,
{
headers: {
'X-API-KEY': apiKey,
},
}
);

const text = await response.text();
const data = text ? JSON.parse(text) : {};

if (!response.ok) {
return NextResponse.json(data, { status: response.status });
}

return NextResponse.json(data, { status: response.status });
} catch (error: any) {
return NextResponse.json(
{ success: false, error: error.message, data: null },
{ status: 500 }
);
}
}

// DELETE /api/collection/[collection_id] - Delete a collection
export async function DELETE(
request: Request,
{ params }: { params: Promise<{ collection_id: string }> }
) {
const { collection_id } = await params;
const apiKey = request.headers.get('X-API-KEY');

if (!apiKey) {
return NextResponse.json(
{ error: 'Missing X-API-KEY header' },
{ status: 401 }
);
}

try {
const response = await fetch(
`${backendUrl}/api/v1/collections/${collection_id}`,
{
method: 'DELETE',
headers: {
'X-API-KEY': apiKey,
},
}
);

const text = await response.text();
const data = text ? JSON.parse(text) : { success: true };

if (!response.ok) {
return NextResponse.json(data, { status: response.status });
}

return NextResponse.json(data, { status: response.status });
} catch (error: any) {
return NextResponse.json(
{ success: false, error: error.message, data: null },
{ status: 500 }
);
}
}
44 changes: 44 additions & 0 deletions app/api/collections/jobs/[job_id]/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { NextResponse } from 'next/server';

const backendUrl = process.env.NEXT_PUBLIC_BACKEND_URL || 'http://localhost:8000';

// GET /api/collections/jobs/[job_id] - Get collection job status
export async function GET(
request: Request,
{ params }: { params: Promise<{ job_id: string }> }
) {
const { job_id } = await params;
const apiKey = request.headers.get('X-API-KEY');

if (!apiKey) {
return NextResponse.json(
{ error: 'Missing X-API-KEY header' },
{ status: 401 }
);
}

try {
const response = await fetch(
`${backendUrl}/api/v1/collections/jobs/${job_id}`,
{
headers: {
'X-API-KEY': apiKey,
},
}
);

const text = await response.text();
const data = text ? JSON.parse(text) : {};

if (!response.ok) {
return NextResponse.json(data, { status: response.status });
}

return NextResponse.json(data, { status: response.status });
} catch (error: any) {
return NextResponse.json(
{ success: false, error: error.message, data: null },
{ status: 500 }
);
}
}
84 changes: 84 additions & 0 deletions app/api/collections/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
import { NextRequest, NextResponse } from 'next/server';


export async function GET(request: Request) {
const backendUrl = process.env.NEXT_PUBLIC_BACKEND_URL || 'http://localhost:8000';
const apiKey = request.headers.get('X-API-KEY');

if (!apiKey) {
return NextResponse.json(
{ error: 'Missing X-API-KEY header' },
{ status: 401 }
);
}

try {
const response = await fetch(`${backendUrl}/api/v1/collections/`, {
headers: {
'X-API-KEY': apiKey,
},
});

// Handle empty responses (204 No Content, etc.)
const text = await response.text();
const data = text ? JSON.parse(text) : [];

if (!response.ok) {
return NextResponse.json(data, { status: response.status });
}

return NextResponse.json(data, { status: response.status });
} catch (error: any) {
return NextResponse.json(
{ success: false, error: error.message, data: null },
{ status: 500 }
);
}
}

export async function POST(request: NextRequest) {
try {
// Get the API key from request headers
const apiKey = request.headers.get('X-API-KEY');

if (!apiKey) {
return NextResponse.json(
{ error: 'Missing X-API-KEY header' },
{ status: 401 }
);
}

// Get the JSON body from the request
const body = await request.json();

// Get backend URL from environment variable
const backendUrl = process.env.NEXT_PUBLIC_BACKEND_URL || 'http://localhost:8000';

// Forward the request to the actual backend
const response = await fetch(`${backendUrl}/api/v1/collections/`, {
method: 'POST',
body: JSON.stringify(body),
headers: {
'X-API-KEY': apiKey,
'Content-Type': 'application/json',
},
});

// Handle empty responses (204 No Content, etc.)
const text = await response.text();
const data = text ? JSON.parse(text) : { success: true };

// Return the response with the same status code
if (!response.ok) {
return NextResponse.json(data, { status: response.status });
}

return NextResponse.json(data, { status: response.status });
} catch (error: any) {
console.error('Proxy error:', error);
return NextResponse.json(
{ error: 'Failed to forward request to backend', details: error.message },
{ status: 500 }
);
}
}
1 change: 1 addition & 0 deletions app/components/Sidebar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ export default function Sidebar({ collapsed, activeRoute = '/evaluations' }: Sid
]
},
{ name: 'Documents', route: '/document' },
{ name: 'Knowledge Base', route: '/knowledge-base' },
// { name: 'Model Testing', route: '/model-testing', comingSoon: true },
// { name: 'Guardrails', route: '/guardrails', comingSoon: true },
// { name: 'Redteaming', route: '/redteaming', comingSoon: true },
Expand Down
22 changes: 15 additions & 7 deletions app/components/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,22 @@
/**
* Converts a date string to IST timezone and formats it
* @param dateString - Date string from backend (in IST format but without timezone info)
* @returns Formatted date string in en-GB locale with 12-hour format
* @returns Formatted date string (e.g., "15 Jan 2024, 14:30")
*/
export const formatDate = (dateString: string): string => {
// Parse the date string and treat it as IST time
const date = new Date(dateString);
// Add 5.5 hours (IST offset) since the input is already in IST but parsed as UTC
const istDate = new Date(date.getTime() + (5.5 * 60 * 60 * 1000));
return istDate.toLocaleString('en-GB', { hour12: true });
export const formatDate = (dateString?: string): string => {
if (!dateString) return 'N/A';
try {
const date = new Date(dateString);
const istDate = new Date(date.getTime() + (5.5 * 60 * 60 * 1000));
const day = istDate.getDate();
const month = istDate.toLocaleDateString('en-US', { month: 'short' });
const year = istDate.getFullYear();
const hours = String(istDate.getHours()).padStart(2, '0');
const minutes = String(istDate.getMinutes()).padStart(2, '0');
return `${day} ${month} ${year}, ${hours}:${minutes}`;
} catch {
Comment on lines +10 to +21
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

find . -name "utils.ts" -path "*/app/components/*" -type f

Repository: ProjectTech4DevAI/kaapi-frontend

Length of output: 101


🏁 Script executed:

cat -n app/components/utils.ts | head -30

Repository: ProjectTech4DevAI/kaapi-frontend

Length of output: 1356


🏁 Script executed:

# Test JavaScript Date behavior with invalid input
node <<'EOF'
// Test invalid date behavior
const invalidDate = new Date("invalid-date-string");
console.log("Invalid date object:", invalidDate);
console.log("getTime() result:", invalidDate.getTime());
console.log("isNaN(getTime()):", Number.isNaN(invalidDate.getTime()));
console.log("getDate() result:", invalidDate.getDate());
console.log("getHours() result:", invalidDate.getHours());

// Test formatting with NaN values
const result = `${invalidDate.getDate()} ${invalidDate.getHours()}:00`;
console.log("Formatted with NaN:", result);
EOF

Repository: ProjectTech4DevAI/kaapi-frontend

Length of output: 230


🏁 Script executed:

# Search for tests or usages of formatDate
rg "formatDate" --type ts --type tsx -B 2 -A 2

Repository: ProjectTech4DevAI/kaapi-frontend

Length of output: 103


🏁 Script executed:

# Search for tests or usages of formatDate with correct file types
rg "formatDate" -B 2 -A 2

Repository: ProjectTech4DevAI/kaapi-frontend

Length of output: 6249


🏁 Script executed:

# Check if there are test files
find . -name "*.test.ts" -o -name "*.spec.ts" | head -10

Repository: ProjectTech4DevAI/kaapi-frontend

Length of output: 58


Guard against invalid date inputs. new Date() doesn't throw on invalid input—it returns an Invalid Date object with NaN values that render as "NaN NaN:00" in the output. The current try/catch won't prevent this. Add a check for Number.isNaN(date.getTime()) before formatting.

Proposed fix
 export const formatDate = (dateString?: string): string => {
   if (!dateString) return 'N/A';
   try {
     const date = new Date(dateString);
+    if (Number.isNaN(date.getTime())) {
+      return dateString;
+    }
     const istDate = new Date(date.getTime() + (5.5 * 60 * 60 * 1000));
     const day = istDate.getDate();
     const month = istDate.toLocaleDateString('en-US', { month: 'short' });
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export const formatDate = (dateString?: string): string => {
if (!dateString) return 'N/A';
try {
const date = new Date(dateString);
const istDate = new Date(date.getTime() + (5.5 * 60 * 60 * 1000));
const day = istDate.getDate();
const month = istDate.toLocaleDateString('en-US', { month: 'short' });
const year = istDate.getFullYear();
const hours = String(istDate.getHours()).padStart(2, '0');
const minutes = String(istDate.getMinutes()).padStart(2, '0');
return `${day} ${month} ${year}, ${hours}:${minutes}`;
} catch {
export const formatDate = (dateString?: string): string => {
if (!dateString) return 'N/A';
try {
const date = new Date(dateString);
if (Number.isNaN(date.getTime())) {
return dateString;
}
const istDate = new Date(date.getTime() + (5.5 * 60 * 60 * 1000));
const day = istDate.getDate();
const month = istDate.toLocaleDateString('en-US', { month: 'short' });
const year = istDate.getFullYear();
const hours = String(istDate.getHours()).padStart(2, '0');
const minutes = String(istDate.getMinutes()).padStart(2, '0');
return `${day} ${month} ${year}, ${hours}:${minutes}`;
} catch {
🤖 Prompt for AI Agents
In `@app/components/utils.ts` around lines 10 - 21, The formatDate function
currently assumes new Date(dateString) is valid; update formatDate to guard
against invalid dates by checking Number.isNaN(date.getTime()) (or
isNaN(date.getTime())) right after creating const date = new Date(dateString)
and before applying the IST offset and formatting; if the check fails return
'N/A' (or the current fallback) so you never format an Invalid Date and produce
NaN values. Ensure you keep the existing IST conversion and formatting logic in
formatDate unchanged except for this validity guard.

return dateString;
}
};

/**
Expand Down
34 changes: 1 addition & 33 deletions app/document/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { useRouter } from 'next/navigation'
import { APIKey, STORAGE_KEY } from '../keystore/page';
import Sidebar from '../components/Sidebar';
import { useToast } from '../components/Toast';
import { formatDate } from '../components/utils';

// Backend response interface
export interface Document {
Expand Down Expand Up @@ -367,22 +368,6 @@ function DocumentListing({
currentPage,
onPageChange,
}: DocumentListingProps) {
const formatDate = (dateString?: string) => {
if (!dateString) return 'N/A';
try {
const date = new Date(dateString);
const istDate = new Date(date.getTime() + (5.5 * 60 * 60 * 1000));
const day = istDate.getDate();
const month = istDate.toLocaleDateString('en-US', { month: 'short' });
const year = istDate.getFullYear();
const hours = String(istDate.getHours()).padStart(2, '0');
const minutes = String(istDate.getMinutes()).padStart(2, '0');
return `${day} ${month} ${year}, ${hours}:${minutes}`;
} catch {
return dateString;
}
};

return (
<div className="h-full flex flex-col">
<div className="p-4 border-b" style={{ backgroundColor: 'hsl(0, 0%, 100%)', borderColor: 'hsl(0, 0%, 85%)' }}>
Expand Down Expand Up @@ -594,23 +579,6 @@ function DocumentPreview({ document, isLoading }: DocumentPreviewProps) {
setImageLoadError(false);
}, [document?.id]);

const formatDate = (dateString?: string) => {
if (!dateString) return 'N/A';
try {
const date = new Date(dateString);
// Add 5.5 hours (IST offset) since the input is already in IST but parsed as UTC
const istDate = new Date(date.getTime() + (5.5 * 60 * 60 * 1000));
const day = istDate.getDate();
const month = istDate.toLocaleDateString('en-US', { month: 'short' });
const year = istDate.getFullYear();
const hours = String(istDate.getHours()).padStart(2, '0');
const minutes = String(istDate.getMinutes()).padStart(2, '0');
return `${day} ${month} ${year}, ${hours}:${minutes}`;
} catch {
return dateString;
}
};

const getFileExtension = (filename: string) => {
const parts = filename.split('.');
return parts.length > 1 ? parts[parts.length - 1].toLowerCase() : '';
Expand Down
Loading