Skip to content

Knowledge base: Creation and deletion UI#28

Open
nishika26 wants to merge 4 commits intomainfrom
feature/knowledge_base
Open

Knowledge base: Creation and deletion UI#28
nishika26 wants to merge 4 commits intomainfrom
feature/knowledge_base

Conversation

@nishika26
Copy link
Contributor

@nishika26 nishika26 commented Jan 29, 2026

This is how the UI looks like right now, please take a pull from this branch to try it out, also note that creation and deletion of knowledge base takes a little time to get updated in the UI, while the process already starts in the backend -
image

Summary

  • New Features

    • Introduced Knowledge Base page with collection management UI, enabling users to create, view, and delete collections.
    • Added job status tracking functionality for background operations.
    • Added Knowledge Base menu item to navigation sidebar.
  • Improvements

    • Enhanced date formatting with improved error handling and consistent "DD Mon YYYY, HH:MM" display format.

Summary by CodeRabbit

  • New Features

    • Launched Knowledge Base for creating, organizing, viewing, and deleting document collections
    • Added Knowledge Base navigation item and intuitive create/select/preview flows
    • Document picker, preview modal, and confirmation dialogs for secure operations
  • Improvements

    • Optimistic UI with background job polling to surface collection status and reconcile results
    • Consistent, user-friendly date formatting across listings and previews
    • Persistent API key handling for authenticated collection operations

@coderabbitai
Copy link

coderabbitai bot commented Jan 29, 2026

📝 Walkthrough

Walkthrough

Adds a Knowledge Base UI and multiple Next.js API proxy routes for collections and job status, centralizes date formatting, and adds a Knowledge Base menu item to the sidebar. API routes forward X-API-KEY to the backend and propagate backend responses and statuses.

Changes

Cohort / File(s) Summary
Collections API (list/create)
app/api/collections/route.ts
Added GET to proxy listing and POST to create collections; validates X-API-KEY, forwards requests to backendUrl/api/v1/collections/, handles empty responses, and returns backend status and payload.
Collection item API (get/delete)
app/api/collections/[collection_id]/route.ts
Added GET (with ?include_docs=true) and DELETE handlers; both validate X-API-KEY, forward to backend, parse responses (defaulting body when empty), and return appropriate HTTP statuses.
Job status API
app/api/collections/jobs/[job_id]/route.ts
Added GET handler that proxies job-status requests to backendUrl/api/v1/collections/jobs/{job_id}, forwards X-API-KEY, returns backend payload and status, and handles errors.
Knowledge Base page
app/knowledge-base/page.tsx
New client-side KnowledgeBasePage: two-panel UI for collection CRUD, document selection, job polling/reconciliation, optimistic updates, local caching/enrichment, API-key persistence, modals, and document preview.
Date utils & consumers
app/components/utils.ts, app/document/page.tsx
Made formatDate(dateString?: string) optional and robust (returns 'N/A' or original on parse failure), converts to explicit "day month year, HH:MM" format; app/document/page.tsx now imports and uses this utility.
Sidebar navigation
app/components/Sidebar.tsx
Added a new "Knowledge base" submenu item under Capabilities routing to /knowledge-base.

Sequence Diagram

sequenceDiagram
    participant Client as Client/Browser
    participant APIRoute as API Route (Next.js)
    participant Backend as Backend Service
    participant Poller as Polling Component

    Client->>APIRoute: POST /api/collections (X-API-KEY, body)
    APIRoute->>Backend: POST /api/v1/collections (forward key & body)
    Backend-->>APIRoute: 200 { job_id }
    APIRoute-->>Client: 200 { job_id }

    Client->>Poller: Start polling job_id
    loop until job complete
        Poller->>APIRoute: GET /api/collections/jobs/{job_id} (X-API-KEY)
        APIRoute->>Backend: GET /api/v1/collections/jobs/{job_id}
        Backend-->>APIRoute: job status
        APIRoute-->>Poller: job status
        Poller->>Client: update UI
    end

    Client->>APIRoute: GET /api/collections/{collection_id} (X-API-KEY)
    APIRoute->>Backend: GET /api/v1/collections/{collection_id}?include_docs=true
    Backend-->>APIRoute: collection + docs
    APIRoute-->>Client: collection payload
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

Suggested reviewers

  • Prajna1999
  • vprashrex

Poem

🐰 I hopped with keys held snug and bright,
I polled the jobs through day and night,
I stitched the dates and menus new,
Collections grew from hopeful dew,
hop hop — knowledge blooms in light!

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 42.86% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: introducing a Knowledge Base page with creation and deletion UI for collections, which aligns with the substantial changes across multiple files including the new knowledge-base page component and related API routes.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feature/knowledge_base

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@nishika26 nishika26 changed the base branch from main to feature/doc_upload January 30, 2026 05:26
@nishika26 nishika26 changed the base branch from feature/doc_upload to main January 31, 2026 07:45
@nishika26 nishika26 marked this pull request as ready for review February 2, 2026 05:13
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In `@app/components/utils.ts`:
- Around line 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.

In `@app/knowledge-base/page.tsx`:
- Around line 170-188: The fetchCollections function assumes the API returns an
object with result.data, so when /api/collections returns a raw array the UI
shows zero items; change the parsing to accept both shapes by treating the
response as const collections = result.data || result || [] before
enriching—then continue to call enrichCollectionWithCache on that array and pass
the enrichedCollections to setCollections.

Comment on lines +10 to +21
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 {
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.

Comment on lines 170 to 188
// Fetch collections
const fetchCollections = async () => {
if (!apiKey) return;

setIsLoading(true);
try {
const response = await fetch('/api/collections', {
headers: { 'X-API-KEY': apiKey.key },
});

if (response.ok) {
const result = await response.json();
const collections = result.data || [];

// Enrich collections with cached names and live status
const enrichedCollections = await Promise.all(
collections.map(collection => enrichCollectionWithCache(collection))
);
setCollections(enrichedCollections);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Handle array responses from /api/collections. If the API returns a raw array (as the proxy route can), result.data is undefined and the UI will show zero collections. Mirror the document fetch pattern to accept both shapes.

Proposed fix
       if (response.ok) {
         const result = await response.json();
-        const collections = result.data || [];
+        const collections = Array.isArray(result) ? result : (result.data || []);
 
         // Enrich collections with cached names and live status
         const enrichedCollections = await Promise.all(
           collections.map(collection => enrichCollectionWithCache(collection))
📝 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
// Fetch collections
const fetchCollections = async () => {
if (!apiKey) return;
setIsLoading(true);
try {
const response = await fetch('/api/collections', {
headers: { 'X-API-KEY': apiKey.key },
});
if (response.ok) {
const result = await response.json();
const collections = result.data || [];
// Enrich collections with cached names and live status
const enrichedCollections = await Promise.all(
collections.map(collection => enrichCollectionWithCache(collection))
);
setCollections(enrichedCollections);
// Fetch collections
const fetchCollections = async () => {
if (!apiKey) return;
setIsLoading(true);
try {
const response = await fetch('/api/collections', {
headers: { 'X-API-KEY': apiKey.key },
});
if (response.ok) {
const result = await response.json();
const collections = Array.isArray(result) ? result : (result.data || []);
// Enrich collections with cached names and live status
const enrichedCollections = await Promise.all(
collections.map(collection => enrichCollectionWithCache(collection))
);
setCollections(enrichedCollections);
🤖 Prompt for AI Agents
In `@app/knowledge-base/page.tsx` around lines 170 - 188, The fetchCollections
function assumes the API returns an object with result.data, so when
/api/collections returns a raw array the UI shows zero items; change the parsing
to accept both shapes by treating the response as const collections =
result.data || result || [] before enriching—then continue to call
enrichCollectionWithCache on that array and pass the enrichedCollections to
setCollections.

@nishika26 nishika26 self-assigned this Feb 2, 2026
@nishika26 nishika26 added the enhancement New feature or request label Feb 2, 2026
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In `@app/knowledge-base/page.tsx`:
- Around line 598-609: The onChange handler for the batch size input can set
batchSize to NaN because parseInt(e.target.value) may return NaN; update the
handler used with setBatchSize so you first coerce/validate the parsed value
(e.g., const n = parseInt(e.target.value); const valid = Number.isNaN(n) ? 1 :
n) and then call setBatchSize(Math.max(1, valid)) — modify the inline onChange
arrow function that currently uses setBatchSize(Math.max(1,
parseInt(e.target.value) || 1)) to ensure NaN is handled explicitly before
Math.max.
- Around line 270-275: In handleConfirmCreate add an explicit null guard for
apiKey before using apiKey.key: check if apiKey is null (e.g., if (!apiKey) {
handle the error/return early }) and only call fetch('/api/collections', ...)
when apiKey is non-null; update any UI state (close modal or set an error) in
the early-return branch so TypeScript no longer complains about accessing
apiKey.key and future regressions are prevented.
🧹 Nitpick comments (4)
app/knowledge-base/page.tsx (4)

9-17: Duplicate Document interface definition.

This interface is identical to the one in app/document/page.tsx. Consider importing it from there or extracting to a shared types file to avoid drift and reduce maintenance overhead.

-export interface Document {
-  id: string;
-  fname: string;
-  object_store_url: string;
-  signed_url?: string;
-  file_size?: number;
-  inserted_at?: string;
-  updated_at?: string;
-}
+import { Document } from '@/app/document/page';

79-141: Sequential API calls in enrichCollectionWithCache may cause slow loading.

This function is called via Promise.all on line 185-186, but internally it performs sequential API calls within loops (lines 99, 120). For users with many collections, this creates a waterfall of network requests during enrichment.

Consider batching job status requests or fetching all job statuses in a single call if the backend supports it, then matching locally.


367-372: Missing dependencies in useEffect may cause stale closures.

The effect depends on apiKey but calls fetchCollections and fetchDocuments which are recreated on each render. Either add them to the dependency array with useCallback, or inline the fetch logic.

+  const fetchCollections = useCallback(async () => {
+    // ... existing implementation
+  }, [apiKey]);
+
+  const fetchDocuments = useCallback(async () => {
+    // ... existing implementation  
+  }, [apiKey]);
+
   useEffect(() => {
     if (apiKey) {
       fetchCollections();
       fetchDocuments();
     }
-  }, [apiKey]);
+  }, [apiKey, fetchCollections, fetchDocuments]);

Alternatively, suppress the lint warning if the current behavior is intentional, but document why.


707-711: Use theme color instead of hardcoded hex value.

The delete button uses hardcoded #ef4444 while the project has colors.status.error (#dc2626) defined in the theme. Consider using the theme color for consistency.

                 style={{
                   backgroundColor: 'transparent',
-                  color: '#ef4444',
-                  border: '1px solid `#ef4444`',
+                  color: colors.status.error,
+                  border: `1px solid ${colors.status.error}`,
                 }}

Comment on lines +270 to +275
const response = await fetch('/api/collections', {
method: 'POST',
headers: {
'X-API-KEY': apiKey.key,
'Content-Type': 'application/json',
},
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

Add null guard for apiKey in handleConfirmCreate.

TypeScript will flag apiKey.key as potentially null since apiKey is typed as APIKey | null. Although the modal is only shown after handleCreateClick validates apiKey, adding an explicit guard improves type safety and prevents future regressions.

   const handleConfirmCreate = async () => {
     setShowConfirmCreate(false);
+    if (!apiKey) return;
     setIsCreating(true);
     try {
       const response = await fetch('/api/collections', {
🤖 Prompt for AI Agents
In `@app/knowledge-base/page.tsx` around lines 270 - 275, In handleConfirmCreate
add an explicit null guard for apiKey before using apiKey.key: check if apiKey
is null (e.g., if (!apiKey) { handle the error/return early }) and only call
fetch('/api/collections', ...) when apiKey is non-null; update any UI state
(close modal or set an error) in the early-return branch so TypeScript no longer
complains about accessing apiKey.key and future regressions are prevented.

Comment on lines 598 to 609
<input
type="number"
value={batchSize}
onChange={(e) => setBatchSize(Math.max(1, parseInt(e.target.value) || 1))}
min="1"
className="w-full px-4 py-2 rounded-md border text-sm"
style={{
borderColor: colors.border,
backgroundColor: colors.bg.secondary,
color: colors.text.primary,
}}
/>
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

Handle NaN when parsing batch size input.

parseInt(e.target.value) returns NaN for non-numeric input, and Math.max(1, NaN) evaluates to NaN. This could result in an invalid batchSize being sent to the API.

-                onChange={(e) => setBatchSize(Math.max(1, parseInt(e.target.value) || 1))}
+                onChange={(e) => {
+                  const parsed = parseInt(e.target.value, 10);
+                  setBatchSize(Number.isNaN(parsed) ? 1 : Math.max(1, parsed));
+                }}
📝 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
<input
type="number"
value={batchSize}
onChange={(e) => setBatchSize(Math.max(1, parseInt(e.target.value) || 1))}
min="1"
className="w-full px-4 py-2 rounded-md border text-sm"
style={{
borderColor: colors.border,
backgroundColor: colors.bg.secondary,
color: colors.text.primary,
}}
/>
<input
type="number"
value={batchSize}
onChange={(e) => {
const parsed = parseInt(e.target.value, 10);
setBatchSize(Number.isNaN(parsed) ? 1 : Math.max(1, parsed));
}}
min="1"
className="w-full px-4 py-2 rounded-md border text-sm"
style={{
borderColor: colors.border,
backgroundColor: colors.bg.secondary,
color: colors.text.primary,
}}
/>
🤖 Prompt for AI Agents
In `@app/knowledge-base/page.tsx` around lines 598 - 609, The onChange handler for
the batch size input can set batchSize to NaN because parseInt(e.target.value)
may return NaN; update the handler used with setBatchSize so you first
coerce/validate the parsed value (e.g., const n = parseInt(e.target.value);
const valid = Number.isNaN(n) ? 1 : n) and then call setBatchSize(Math.max(1,
valid)) — modify the inline onChange arrow function that currently uses
setBatchSize(Math.max(1, parseInt(e.target.value) || 1)) to ensure NaN is
handled explicitly before Math.max.

@nishika26 nishika26 changed the title Feature/knowledge base Knowledge base: Creation and deletion UI Feb 4, 2026
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In `@app/knowledge-base/page.tsx`:
- Around line 631-635: The click handler currently always calls
fetchCollectionDetails(collection.id) which triggers failing API calls for
optimistic collections whose ids start with "optimistic-"; update the onClick
logic in the component using setShowCreateForm and setShowDocumentPicker so it
only calls fetchCollectionDetails(collection.id) when collection.id does NOT
startWith "optimistic-"; leave the state updates (setShowCreateForm(false),
setShowDocumentPicker(false)) unchanged and avoid making the network call for
optimistic entries to prevent errors and flicker.
- Around line 667-690: The delete button currently appears for optimistic
(pending) collections and triggers handleDeleteCollection(collection.id) which
calls the DELETE API with an optimistic-* id and fails; update the render logic
to suppress the button for optimistic entries (e.g., when collection.id
startsWith("optimistic-") or collection.pending is true) or change
handleDeleteCollection to detect optimistic ids and remove the collection from
local state without calling the API; locate the button block that references
handleDeleteCollection and collection.id and add the conditional to hide the
button (or route to a local-delete branch) so optimistic collections are not
sent to the DELETE endpoint.
🧹 Nitpick comments (5)
app/knowledge-base/page.tsx (5)

9-17: Consider consolidating the duplicate Document interface.

This Document interface is identical to the one exported from app/document/page.tsx. Duplicating type definitions can lead to drift over time. Consider importing from a shared types file or directly from the document page.

-export interface Document {
-  id: string;
-  fname: string;
-  object_store_url: string;
-  signed_url?: string;
-  file_size?: number;
-  inserted_at?: string;
-  updated_at?: string;
-}
+import { Document } from '../document/page';

Alternatively, create a shared types module (e.g., app/types/index.ts) to house common interfaces.


94-119: Sequential API calls in loop may degrade performance.

When the cache has many entries without collection_id, this loop makes sequential fetchJobStatus calls for each one. With a large cache, this can significantly slow down the initial load.

Consider batching these lookups or limiting the number of iterations to avoid excessive API calls during enrichment.


275-333: Polling iterations may overlap if API calls take longer than 3 seconds.

The setInterval callback is async, but setInterval doesn't wait for the previous iteration to complete. If network requests are slow, multiple polling cycles could run concurrently, causing redundant API calls and potential race conditions when updating state.

Consider adding a guard to skip the iteration if the previous one is still in progress:

Proposed fix
+  const isPollingRef = useRef(false);
+
   pollingRef.current = setInterval(async () => {
+    if (isPollingRef.current) return;
+    isPollingRef.current = true;
+    try {
       const currentApiKey = apiKeyRef.current;
       // ... existing logic ...
+    } finally {
+      isPollingRef.current = false;
+    }
   }, 3000);

496-501: useEffect has missing dependencies that may cause linter warnings.

fetchCollections and fetchDocuments are called inside this effect but are not listed as dependencies. Since these functions are recreated on each render, adding them would cause infinite loops. Consider wrapping them with useCallback to stabilize their references, or add an ESLint disable comment if intentional.

+  // eslint-disable-next-line react-hooks/exhaustive-deps
   useEffect(() => {
     if (apiKey) {
       fetchCollections();
       fetchDocuments();
     }
   }, [apiKey]);

1135-1141: Consider adding sandbox attribute to the iframe for defense-in-depth.

The iframe loads document content from signed URLs. Adding a sandbox attribute restricts the embedded content's capabilities, providing an additional security layer.

   <iframe
     key={previewDoc.id}
     src={previewDoc.signed_url}
     title={previewDoc.fname}
     className="w-full h-full"
-    style={{ border: 'none' }}
+    style={{ border: 'none' }}
+    sandbox="allow-same-origin allow-scripts"
   />

Note: Adjust sandbox permissions based on what the previewed documents require (e.g., allow-scripts only if needed for PDF viewers).

Comment on lines +631 to +635
onClick={() => {
setShowCreateForm(false);
setShowDocumentPicker(false);
fetchCollectionDetails(collection.id);
}}
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

Avoid fetching collection details for optimistic entries.

When clicking an optimistic collection (ID starts with optimistic-), fetchCollectionDetails makes an API call that will fail since the collection doesn't exist on the backend yet. This causes unnecessary error logs and potential UI flicker.

Proposed fix
   onClick={() => {
     setShowCreateForm(false);
     setShowDocumentPicker(false);
-    fetchCollectionDetails(collection.id);
+    if (collection.id.startsWith('optimistic-')) {
+      setSelectedCollection(collection);
+    } else {
+      fetchCollectionDetails(collection.id);
+    }
   }}
📝 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
onClick={() => {
setShowCreateForm(false);
setShowDocumentPicker(false);
fetchCollectionDetails(collection.id);
}}
onClick={() => {
setShowCreateForm(false);
setShowDocumentPicker(false);
if (collection.id.startsWith('optimistic-')) {
setSelectedCollection(collection);
} else {
fetchCollectionDetails(collection.id);
}
}}
🤖 Prompt for AI Agents
In `@app/knowledge-base/page.tsx` around lines 631 - 635, The click handler
currently always calls fetchCollectionDetails(collection.id) which triggers
failing API calls for optimistic collections whose ids start with "optimistic-";
update the onClick logic in the component using setShowCreateForm and
setShowDocumentPicker so it only calls fetchCollectionDetails(collection.id)
when collection.id does NOT startWith "optimistic-"; leave the state updates
(setShowCreateForm(false), setShowDocumentPicker(false)) unchanged and avoid
making the network call for optimistic entries to prevent errors and flicker.

Comment on lines +667 to +690
<button
onClick={(e) => {
e.stopPropagation();
handleDeleteCollection(collection.id);
}}
className="p-1.5 rounded-md transition-colors flex-shrink-0"
style={{
borderWidth: '1px',
borderColor: 'hsl(8, 86%, 80%)',
backgroundColor: colors.bg.primary,
color: 'hsl(8, 86%, 40%)',
}}
onMouseEnter={(e) => {
e.currentTarget.style.backgroundColor = 'hsl(8, 86%, 95%)';
}}
onMouseLeave={(e) => {
e.currentTarget.style.backgroundColor = colors.bg.primary;
}}
title="Delete Knowledge Base"
>
<svg className="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" />
</svg>
</button>
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

Prevent deletion of optimistic collections.

The delete button is shown for optimistic collections (pending creation), but calling the DELETE API with an optimistic-* ID will fail. Consider hiding the delete button for pending collections or handling removal locally.

Proposed fix - hide delete for optimistic entries
+                  {!collection.id.startsWith('optimistic-') && (
                     <button
                       onClick={(e) => {
                         e.stopPropagation();
                         handleDeleteCollection(collection.id);
                       }}
                       ...
                     </button>
+                  )}
🤖 Prompt for AI Agents
In `@app/knowledge-base/page.tsx` around lines 667 - 690, The delete button
currently appears for optimistic (pending) collections and triggers
handleDeleteCollection(collection.id) which calls the DELETE API with an
optimistic-* id and fails; update the render logic to suppress the button for
optimistic entries (e.g., when collection.id startsWith("optimistic-") or
collection.pending is true) or change handleDeleteCollection to detect
optimistic ids and remove the collection from local state without calling the
API; locate the button block that references handleDeleteCollection and
collection.id and add the conditional to hide the button (or route to a
local-delete branch) so optimistic collections are not sent to the DELETE
endpoint.

@nishika26 nishika26 linked an issue Feb 6, 2026 that may be closed by this pull request
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

UI: Knowledge base creation

1 participant