Skip to content

Documents support in chat#231

Open
dhirenmathur wants to merge 5 commits into
mainfrom
doc
Open

Documents support in chat#231
dhirenmathur wants to merge 5 commits into
mainfrom
doc

Conversation

@dhirenmathur

@dhirenmathur dhirenmathur commented Nov 7, 2025

Copy link
Copy Markdown
Contributor

Summary by CodeRabbit

  • New Features

    • Document attachment support in chat: upload, client-side validation, progress, cancel, previews, metadata (token/page counts)
    • Context usage indicator with token breakdown and warnings
    • Attachment UI: cards with download/remove and a validation modal
    • File-type utilities and token estimation; ChatService attachment APIs (validate, upload, info, download)
    • GitHub OAuth linking flow
  • Bug Fixes

    • Removed duplicate repository selection logic; added safer chat initialization guards

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai

coderabbitai Bot commented Nov 7, 2025

Copy link
Copy Markdown
Contributor

Walkthrough

Adds end-to-end document attachment support (types, file utilities, UI, ChatService APIs, runtime/composer/thread integration) and a client-side GitHub OAuth utility for linking/reauth with Firebase.

Changes

Cohort / File(s) Summary
GitHub OAuth
app/(auth)/utils/github-oauth.ts
New client-side ensureGithubOAuth utility with GithubOAuthResult and GITHUB_PROVIDER_ID; handles linking, force reauth via popup, credential/accessToken extraction, and provider-already-linked handling.
Attachment Types
lib/types/attachment.ts
New attachment/context types: AttachmentType, ValidationResponse, AttachmentUploadResponse, AttachmentMetadata, AttachmentInfo, ContextUsageResponse, DocumentAttachment.
File utilities
lib/utils/fileTypes.ts
New file-type mappings/utilities: SUPPORTED_* sets, MAX_FILE_SIZE, MIME detection, file classification, getFileTypeInfo, formatFileSize, estimateTokens, and getSupportedFileExtensions.
Chat service APIs
services/ChatService.ts
Extended streamMessage(..., attachmentIds) signature; added static methods validateDocument, uploadAttachment, getAttachmentInfo, getContextUsage, downloadAttachment; attachment IDs propagated in FormData.
Message Composer & Runtime
app/(main)/chat/[chatId]/components/MessageComposer.tsx, app/(main)/chat/[chatId]/runtime.ts
Composer: document state, validation modal, upload/progress/cancel, previews, drag/drop/paste handling, and new setPendingDocumentAttachments prop; Runtime: accept documentIds from runConfig and pass to ChatService.streamMessage.
Thread rendering
app/(main)/chat/[chatId]/components/Thread.tsx
Tracks pending/committed document attachments, commits attachments to latest user message, and renders a documentAttachments block (icons, file_name, token/page counts) alongside existing multimodal attachments.
Attachment UI components
components/chat/DocumentAttachmentCard.tsx, components/chat/ValidationErrorModal.tsx, components/chat/ContextUsageIndicator.tsx
New exported components: DocumentAttachmentCard, ValidationErrorModal, and ContextUsageIndicator with props and behaviors for display, validation feedback, and context usage refresh.
Integration & Misc
components/agent-creation/ChatPanel.tsx, app/(main)/newchat/components/step1.tsx, app/(main)/chat/[chatId]/page.tsx, lib/utils.ts, app/(auth)/link-github/page.tsx, app/(auth)/onboarding/page.tsx, components/Layouts/minors/nav-user.tsx, lib/utils/emailValidation.ts
Integration tweaks: pass empty attachmentIds in ChatPanel, repo selection/UI adjustments, stronger agent/project ID guards in chat page, expanded mock user shape, safer GitHub linkWithPopup usage (use captured user), minor DOM/title adjustments, and small internal type cast in emailValidation.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant MessageComposer
    participant ChatService
    participant ValidationModal
    participant Thread

    User->>MessageComposer: Drop/paste/select file
    MessageComposer->>ChatService: validateDocument(conversationId, file)
    alt can_upload == false
        ChatService-->>MessageComposer: ValidationResponse(can_upload=false)
        MessageComposer->>ValidationModal: open(validation)
        ValidationModal->>User: show cannot-upload details
    else can_upload == true
        ChatService-->>MessageComposer: ValidationResponse(can_upload=true)
        MessageComposer->>ChatService: uploadAttachment(file)
        ChatService-->>MessageComposer: AttachmentUploadResponse(id)
        MessageComposer->>MessageComposer: add to documents state
        User->>MessageComposer: Send message
        MessageComposer->>ChatService: streamMessage(..., attachmentIds=[id])
        ChatService->>Thread: stream message data (includes attachment_ids)
        Thread->>ChatService: getAttachmentInfo(id)
        ChatService-->>Thread: AttachmentInfo / metadata
        Thread->>User: render message with attachment block
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested reviewers

  • nndn
  • ASCE-D

Poem

🐰 I found a file, small and bright,
hopped it into chat by moonlight.
Tokens counted, warnings shown,
uploads safe, no fear to own.
Hooray — attachments now take flight! 🥕

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 15.38% 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 'Documents support in chat' accurately and concisely describes the primary change in the PR, which adds comprehensive document attachment functionality throughout the chat application.

✏️ 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 doc

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.

@sonarqubecloud

sonarqubecloud Bot commented Nov 7, 2025

Copy link
Copy Markdown

@github-actions

github-actions Bot commented Nov 7, 2025

Copy link
Copy Markdown

Cloud Run service deployed: https://pr-doc-cj6r7x3fpa-uc.a.run.app

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 7

🧹 Nitpick comments (6)
components/chat/DocumentAttachmentCard.tsx (2)

69-86: Add aria-labels for better accessibility.

The buttons use title attributes for tooltips, but these are not read by screen readers. Consider adding aria-label attributes to improve accessibility.

Apply this diff to add aria-labels:

         {onDownload && (
           <button
             type="button"
             onClick={onDownload}
             className="p-1.5 hover:bg-gray-200 rounded transition-colors"
             title="Download"
+            aria-label={`Download ${attachment.file_name}`}
           >
             <Download className="w-4 h-4" />
           </button>
         )}

         <button
           type="button"
           onClick={onRemove}
           className="p-1.5 hover:bg-red-100 text-red-600 rounded transition-colors"
           title="Remove"
+          aria-label={`Remove ${attachment.file_name}`}
         >
           <X className="w-4 h-4" />
         </button>

22-23: Consider using the FileText icon component as fallback.

Line 2 imports FileText from lucide-react, but it's not used. Consider using <FileText /> as the fallback icon instead of the emoji '📎' for visual consistency with other lucide-react icons in the codebase.

-  const icon = fileTypeInfo?.icon || '📎';
+  const iconEmoji = fileTypeInfo?.icon;

Then in the JSX:

-        <div className="text-2xl flex-shrink-0">{icon}</div>
+        <div className="text-2xl flex-shrink-0">
+          {iconEmoji || <FileText className="w-6 h-6 text-gray-400" />}
+        </div>
lib/utils/fileTypes.ts (1)

56-60: Consider handling edge case for 0 bytes.

While unlikely in practice, if bytes is 0, the function returns "0 B" which is correct. However, consider if negative values should be handled or if the function should accept bytes: number or bytes: number & { __brand: "positive" } for type safety.

 export function formatFileSize(bytes: number): string {
+  if (bytes < 0) return 'Invalid size';
   if (bytes < 1024) return `${bytes} B`;
app/(main)/chat/[chatId]/runtime.ts (1)

269-270: Type assertion could be unsafe.

The cast to any[] doesn't validate that the objects have the expected structure. If runConfig.custom.documents contains unexpected data, this could lead to runtime errors in the UI when rendering attachments.

Consider adding runtime validation or using a type guard:

// Define expected structure
interface DocumentAttachmentData {
  id: string;
  file_name: string;
  mime_type: string;
  file_size: number;
  token_count?: number;
  metadata?: any;
}

// Add type guard
function isDocumentAttachment(obj: any): obj is DocumentAttachmentData {
  return obj && 
    typeof obj.id === 'string' &&
    typeof obj.file_name === 'string' &&
    typeof obj.mime_type === 'string' &&
    typeof obj.file_size === 'number';
}

// Use in extraction
const documentAttachments = (
  (message.runConfig?.custom?.documents as any[]) || []
).filter(isDocumentAttachment);

Alternatively, verify that the upstream code setting runConfig.custom.documents ensures proper typing.

app/(main)/chat/[chatId]/components/Thread.tsx (1)

360-395: Reuse the shared attachment card for consistency

Now that DocumentAttachmentCard exists, consider rendering it here instead of hand-rolling the emoji UI. That keeps formatting/icon logic single-sourced and avoids the two code paths drifting over time.

app/(main)/chat/[chatId]/components/MessageComposer.tsx (1)

300-302: Drop the debug logging before merge

The new console.log statements are handy during development but will spam the console in production. Let’s remove them before we ship.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d363e0e and 772e4cb.

📒 Files selected for processing (12)
  • app/(auth)/utils/github-oauth.ts (1 hunks)
  • app/(main)/chat/[chatId]/components/MessageComposer.tsx (9 hunks)
  • app/(main)/chat/[chatId]/components/Thread.tsx (3 hunks)
  • app/(main)/chat/[chatId]/runtime.ts (3 hunks)
  • app/(main)/newchat/components/step1.tsx (1 hunks)
  • components/agent-creation/ChatPanel.tsx (1 hunks)
  • components/chat/ContextUsageIndicator.tsx (1 hunks)
  • components/chat/DocumentAttachmentCard.tsx (1 hunks)
  • components/chat/ValidationErrorModal.tsx (1 hunks)
  • lib/types/attachment.ts (1 hunks)
  • lib/utils/fileTypes.ts (1 hunks)
  • services/ChatService.ts (4 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx}: Use PascalCase for component names and camelCase for utility names
Order imports: React/Next imports first, then components, then utilities
Use Next.js error boundaries, optional chaining, and nullish coalescing for error handling

Files:

  • components/chat/DocumentAttachmentCard.tsx
  • components/agent-creation/ChatPanel.tsx
  • lib/utils/fileTypes.ts
  • components/chat/ContextUsageIndicator.tsx
  • services/ChatService.ts
  • app/(main)/chat/[chatId]/components/MessageComposer.tsx
  • components/chat/ValidationErrorModal.tsx
  • app/(main)/chat/[chatId]/components/Thread.tsx
  • lib/types/attachment.ts
  • app/(main)/newchat/components/step1.tsx
  • app/(main)/chat/[chatId]/runtime.ts
  • app/(auth)/utils/github-oauth.ts
**/*.tsx

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.tsx: Use Tailwind CSS for styling and the cn utility for conditional classes
Use functional components with TypeScript FC types
Prefer destructuring props at the component level
Use the "use client" directive for client components

Files:

  • components/chat/DocumentAttachmentCard.tsx
  • components/agent-creation/ChatPanel.tsx
  • components/chat/ContextUsageIndicator.tsx
  • app/(main)/chat/[chatId]/components/MessageComposer.tsx
  • components/chat/ValidationErrorModal.tsx
  • app/(main)/chat/[chatId]/components/Thread.tsx
  • app/(main)/newchat/components/step1.tsx
🧬 Code graph analysis (6)
components/chat/DocumentAttachmentCard.tsx (3)
lib/types/attachment.ts (1)
  • DocumentAttachment (79-83)
lib/utils/fileTypes.ts (2)
  • getFileTypeInfo (46-54)
  • formatFileSize (56-60)
lib/utils.ts (1)
  • cn (4-6)
components/chat/ContextUsageIndicator.tsx (3)
lib/types/attachment.ts (1)
  • ContextUsageResponse (62-76)
services/ChatService.ts (1)
  • ChatService (8-833)
lib/utils.ts (1)
  • cn (4-6)
services/ChatService.ts (1)
lib/types/attachment.ts (4)
  • ValidationResponse (4-18)
  • AttachmentUploadResponse (20-26)
  • AttachmentInfo (51-60)
  • ContextUsageResponse (62-76)
app/(main)/chat/[chatId]/components/MessageComposer.tsx (6)
lib/types/attachment.ts (2)
  • DocumentAttachment (79-83)
  • ValidationResponse (4-18)
lib/utils/fileTypes.ts (4)
  • MAX_FILE_SIZE (36-36)
  • isDocumentType (38-40)
  • isImageType (42-44)
  • getSupportedFileExtensions (78-82)
services/ChatService.ts (1)
  • ChatService (8-833)
components/chat/ValidationErrorModal.tsx (1)
  • ValidationErrorModal (24-134)
components/chat/ContextUsageIndicator.tsx (1)
  • ContextUsageIndicator (19-135)
components/chat/DocumentAttachmentCard.tsx (1)
  • DocumentAttachmentCard (15-90)
components/chat/ValidationErrorModal.tsx (4)
lib/types/attachment.ts (1)
  • ValidationResponse (4-18)
components/ui/dialog.tsx (5)
  • Dialog (113-113)
  • DialogContent (118-118)
  • DialogHeader (119-119)
  • DialogTitle (121-121)
  • DialogDescription (122-122)
lib/utils/fileTypes.ts (1)
  • formatFileSize (56-60)
components/ui/button.tsx (1)
  • Button (56-56)
app/(main)/chat/[chatId]/runtime.ts (1)
services/ChatService.ts (1)
  • ChatService (8-833)
🔇 Additional comments (6)
app/(main)/newchat/components/step1.tsx (1)

490-500: LGTM - Consistent repository linking across environments.

Making the "Link new repository" option always available (regardless of environment) is a good change. This allows users to link repositories via the GitHub app in all environments, while environment-specific options (public repos for production, local repos for localhost) remain appropriately gated.

app/(auth)/utils/github-oauth.ts (1)

28-48: Well-structured OAuth flow with proper early returns.

The function correctly handles the already-linked scenario with an early return and provides a forceReauth option for token refresh. The scope configuration is appropriate for GitHub integration.

lib/utils/fileTypes.ts (1)

1-44: Well-organized file type definitions and predicates.

The type definitions for documents and images are comprehensive and well-structured with appropriate metadata (extensions, icons, labels). The predicate functions provide clean type checking.

app/(main)/chat/[chatId]/runtime.ts (2)

297-306: Good debug logging for document attachment flow.

The debug logs will help troubleshoot issues with document attachments. The extraction and passing of documentIds to ChatService.streamMessage correctly integrates document support into the messaging pipeline.


234-234: Empty attachment array is appropriate for onMessage.

The onMessage function doesn't handle attachments yet, so passing an empty array maintains API compatibility while keeping the function focused on simple text messages.

components/agent-creation/ChatPanel.tsx (1)

187-208: Attachment parameter wiring LGTM

Adding the empty attachment array keeps this call site in sync with the expanded ChatService.streamMessage signature—no issues spotted.

Comment on lines +55 to +61
const credential = GithubAuthProvider.credentialFromResult(result);
const accessToken =
credential?.accessToken ??
(result as any)?._tokenResponse?.oauthAccessToken ??
null;

const providerUsername = (result as any)?._tokenResponse?.screenName;

@coderabbitai coderabbitai Bot Nov 7, 2025

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

Accessing undocumented Firebase internal APIs.

Lines 58 and 61 access internal Firebase response fields via (result as any)?._tokenResponse, which are not part of the documented Firebase API. These fields could change or be removed in future Firebase SDK updates, potentially breaking this functionality.

Consider these alternatives:

  1. Use only the documented credential?.accessToken (line 57) and remove the fallback to _tokenResponse.oauthAccessToken
  2. If the fallback is necessary, add a comment explaining why and add defensive error handling
  3. For screenName, consider fetching the GitHub username via the GitHub API using the access token instead of relying on internal Firebase fields

If the undocumented fields are required, add a TODO comment and error handling:

    const accessToken =
      credential?.accessToken ??
+     // TODO: Remove fallback to internal API when Firebase SDK provides documented alternative
      (result as any)?._tokenResponse?.oauthAccessToken ??
      null;

-    const providerUsername = (result as any)?._tokenResponse?.screenName;
+    // TODO: Fetch username via GitHub API instead of internal Firebase field
+    const providerUsername = (result as any)?._tokenResponse?.screenName ?? null;

I'll verify this review comment by inspecting the actual code and researching Firebase SDK documentation.


🏁 Script executed:

#!/bin/bash
# Verify the exact code at the specified lines
if [ -f "app/(auth)/utils/github-oauth.ts" ]; then
  echo "=== Lines 55-61 of app/(auth)/utils/github-oauth.ts ==="
  sed -n '55,61p' "app/(auth)/utils/github-oauth.ts"
  echo -e "\n=== Full file context (lines 50-70) ==="
  sed -n '50,70p' "app/(auth)/utils/github-oauth.ts"
fi

Length of output: 1025


🏁 Script executed:

#!/bin/bash
# Search for all usages of _tokenResponse in the codebase
echo "=== Usages of _tokenResponse in codebase ==="
rg -n "_tokenResponse" --type=ts --type=tsx -C2

Length of output: 72


🏁 Script executed:

#!/bin/bash
# Check the Firebase/authentication imports and version hints
echo "=== package.json dependencies (firebase) ==="
grep -A 5 -B 5 "firebase" package.json 2>/dev/null || echo "Not found in root package.json"

Length of output: 715


🏁 Script executed:

#!/bin/bash
# Search for GitHub OAuth configuration and comments
echo "=== GitHub OAuth context in file ==="
if [ -f "app/(auth)/utils/github-oauth.ts" ]; then
  cat "app/(auth)/utils/github-oauth.ts"
fi

Length of output: 2050


🌐 Web query:

Firebase SDK GithubAuthProvider credentialFromResult _tokenResponse documented API

💡 Result:

Short answer: _tokenResponse is an internal SDK implementation detail — it is not part of the documented, supported public API. The official docs show GithubAuthProvider.credentialFromResult(userCredential) returns an OAuthCredential (with .accessToken) and do not document or promise any _tokenResponse property. [1][2]

References:

  • GithubAuthProvider API (credentialFromResult → OAuthCredential / .accessToken). [1]
  • General OAuthProvider / provider credentialFromResult docs (returns OAuthCredential; no _tokenResponse documented). [2]

Sources:

  1. Firebase JS docs — GithubAuthProvider (credentialFromResult example showing credential.accessToken). [1]
  2. Firebase JS docs — OAuthProvider / auth API reference (credentialFromResult returns OAuthCredential). [2]

🌐 Web query:

Firebase GitHub OAuth provider screenName username authentication 2024 2025

💡 Result:

Short answer: Firebase's GitHub provider does NOT return the GitHub "username"/"login" (screen name) in user.providerData. It returns displayName, email, photoURL and a numeric GitHub uid. To get the GitHub login you must obtain the provider OAuth access token from the sign-in result and call the GitHub API (GET https://api.github.com/user) to read the "login" field.

How (brief):

  • After sign-in get the OAuth credential and access token (e.g. GithubAuthProvider.credentialFromResult(result).accessToken or result.credential.accessToken). [1][2]
  • Call GitHub API /user with Authorization: Bearer and read the "login" property. [2]
  • Note: providerData only exposes providerId, uid (numeric), displayName, email, photoURL — not the GitHub login. [3]

Sources: Firebase docs on GitHub sign-in and credentials / providerData and a community explanation. [1] [2] [3]

References
[1] Firebase — GitHub sign-in docs (access token / credential examples).
[2] Firebase JS API reference (GithubAuthProvider / OAuthCredential examples).
[3] StackOverflow — explanation that providerData lacks GitHub username and you must call GitHub API to resolve it.


Remove reliance on undocumented Firebase internal APIs _tokenResponse.

The _tokenResponse object is an internal SDK implementation detail, not part of the documented public API. Line 58 (fallback to _tokenResponse?.oauthAccessToken) and line 61 (_tokenResponse?.screenName) create maintenance risk and should be replaced with documented alternatives:

  • Line 58: The documented credential?.accessToken from OAuthCredential is the supported public API; remove the fallback to _tokenResponse.oauthAccessToken.
  • Line 61: Firebase's GitHub provider does not return the GitHub username (screenName) in documented fields. Fetch the GitHub login via the GitHub API using the access token (GET https://api.github.com/user), or remove providerUsername if not required.

Replace with:

    const credential = GithubAuthProvider.credentialFromResult(result);
-   const accessToken =
-     credential?.accessToken ??
-     (result as any)?._tokenResponse?.oauthAccessToken ??
-     null;
+   const accessToken = credential?.accessToken ?? null;

-   const providerUsername = (result as any)?._tokenResponse?.screenName;
+   // Fetch GitHub username via GitHub API if required; Firebase does not expose login/screenName
+   const providerUsername = undefined;
📝 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
const credential = GithubAuthProvider.credentialFromResult(result);
const accessToken =
credential?.accessToken ??
(result as any)?._tokenResponse?.oauthAccessToken ??
null;
const providerUsername = (result as any)?._tokenResponse?.screenName;
const credential = GithubAuthProvider.credentialFromResult(result);
const accessToken = credential?.accessToken ?? null;
// Fetch GitHub username via GitHub API if required; Firebase does not expose login/screenName
const providerUsername = undefined;
🤖 Prompt for AI Agents
In app/(auth)/utils/github-oauth.ts around lines 55 to 61, remove reliance on
the undocumented internal _tokenResponse: use only the documented
OAuthCredential public API (i.e. credential?.accessToken) and drop the fallback
to (result as any)?._tokenResponse?.oauthAccessToken; likewise remove reading
providerUsername from _tokenResponse; if you actually need the GitHub username,
perform an authenticated GET to https://api.github.com/user using the access
token (handle missing/null token and errors, parse the "login" field) instead of
accessing internal SDK fields, otherwise delete providerUsername entirely and
propagate null/undefined appropriately.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

does oauth changes required for documents support?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Search for any usage of ensureGithubOAuth in the codebase
echo "=== Searching for usages of ensureGithubOAuth ==="
rg -n "ensureGithubOAuth" --type=ts --type=tsx -C3

echo -e "\n=== Searching for imports from github-oauth.ts ==="
rg -n "from.*github-oauth" --type=ts --type=tsx -C2

echo -e "\n=== Check if there are any connections between OAuth and document features ==="
rg -n "github.*oauth.*(document|attachment|upload)" -i --type=ts --type=tsx

echo -e "\n=== Check what other files were modified in this PR ==="
git diff --name-only origin/main...HEAD 2>/dev/null | head -20

Length of output: 949


🏁 Script executed:

#!/bin/bash
# Search without file type restriction
echo "=== Searching for usages of ensureGithubOAuth ==="
rg -n "ensureGithubOAuth" -C3

echo -e "\n=== Searching for imports from github-oauth ==="
rg -n "from.*github-oauth" -C2

echo -e "\n=== Check link-github and onboarding pages for OAuth usage ==="
echo "--- link-github/page.tsx ---"
cat "app/(auth)/link-github/page.tsx" 2>/dev/null | head -80

echo -e "\n--- onboarding/page.tsx ---"
cat "app/(auth)/onboarding/page.tsx" 2>/dev/null | head -80

Length of output: 6183


@yashkrishan No, the OAuth changes are not required for document support.

Based on my search of the codebase:

  1. The new ensureGithubOAuth utility is not used anywhere - there are no imports or usages of this function in the codebase
  2. Document features are completely independent - they rely on file upload APIs, validation, and attachment handling through ChatService (validateDocument, uploadAttachment, etc.)
  3. Existing OAuth flows don't use the new utility - the link-github and onboarding pages still use Firebase's linkWithPopup directly

It appears that the OAuth refactoring (github-oauth.ts creation and related page updates) was bundled into this PR alongside the document support feature, but they are separate, unrelated changes. The document support functionality works entirely through the file upload and attachment APIs and doesn't require any GitHub OAuth integration.

Comment on lines 297 to 318
const handleSend = () => {
// For now, we'll handle images through the runtime custom config
// The actual image handling will be done in the runtime when onNew is called

// Ensure config is set with current documents before sending
const documentIds = documents.map(doc => doc.id);
console.log('[MessageComposer] handleSend - documentIds:', documentIds);
console.log('[MessageComposer] handleSend - documents:', documents);

composer.setRunConfig({
custom: {
selectedNodes: selectedNodes,
images: images,
documentIds: documentIds,
documents: documents // Pass full document objects for display
}
});

composer.send();
setMessage("");
setSelectedNodes([]);
setImages([]);
setImagePreviews([]);
setDocuments([]);
};

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Keep send disabled while uploads are in flight

Right now handleSend can fire while validating or uploadingDocument are still true. If the user clicks “Send” during that window, the message goes out without the document because the upload hasn’t finished populating documents. Please gate the send path (and button) on those flags so attachments always accompany the message.

🤖 Prompt for AI Agents
In app/(main)/chat/[chatId]/components/MessageComposer.tsx around lines 297 to
318, the handleSend path can run while validating or uploadingDocument are true
which results in messages being sent without attachments; guard the send path by
checking those flags before calling composer.send: if validating ||
uploadingDocument then return early (and ensure the Send button is disabled when
either flag is true); also avoid clearing message/images/documents state until
after a successful send (or composer confirms send) so uploads complete and
documents are included.

Comment on lines +1 to +61
import React, { FC, useEffect, useState } from 'react';
import { ContextUsageResponse } from '@/lib/types/attachment';
import ChatService from '@/services/ChatService';
import { cn } from '@/lib/utils';
import { AlertTriangle } from 'lucide-react';
import {
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger,
} from '@/components/ui/tooltip';

interface ContextUsageIndicatorProps {
conversationId: string;
className?: string;
refreshInterval?: number; // ms
}

export const ContextUsageIndicator: FC<ContextUsageIndicatorProps> = ({
conversationId,
className,
refreshInterval = 30000, // 30 seconds default
}) => {
const [usage, setUsage] = useState<ContextUsageResponse | null>(null);
const [loading, setLoading] = useState(true);

useEffect(() => {
const fetchUsage = async () => {
try {
const data = await ChatService.getContextUsage(conversationId);
setUsage(data);
} catch (error) {
console.error('Error fetching context usage:', error);
} finally {
setLoading(false);
}
};

fetchUsage();
const interval = setInterval(fetchUsage, refreshInterval);
return () => clearInterval(interval);
}, [conversationId, refreshInterval]);

if (loading || !usage) return null;

const getBarColor = () => {
if (usage.warning_level === 'critical') return 'bg-red-500';
if (usage.warning_level === 'approaching') return 'bg-orange-500';
return 'bg-green-500';
};

const getTextColor = () => {
if (usage.warning_level === 'critical') return 'text-red-700';
if (usage.warning_level === 'approaching') return 'text-orange-700';
return 'text-gray-700';
};

return (
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Add the 'use client' directive

This module uses React hooks and runs in the browser. Without 'use client' at the top, Next.js will treat it as a Server Component and the build will fail once imported from a Client Component (per our coding guidelines). Please add the directive.
Apply this diff:

+'use client';
+
 import React, { FC, useEffect, useState } from 'react';
📝 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
import React, { FC, useEffect, useState } from 'react';
import { ContextUsageResponse } from '@/lib/types/attachment';
import ChatService from '@/services/ChatService';
import { cn } from '@/lib/utils';
import { AlertTriangle } from 'lucide-react';
import {
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger,
} from '@/components/ui/tooltip';
interface ContextUsageIndicatorProps {
conversationId: string;
className?: string;
refreshInterval?: number; // ms
}
export const ContextUsageIndicator: FC<ContextUsageIndicatorProps> = ({
conversationId,
className,
refreshInterval = 30000, // 30 seconds default
}) => {
const [usage, setUsage] = useState<ContextUsageResponse | null>(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
const fetchUsage = async () => {
try {
const data = await ChatService.getContextUsage(conversationId);
setUsage(data);
} catch (error) {
console.error('Error fetching context usage:', error);
} finally {
setLoading(false);
}
};
fetchUsage();
const interval = setInterval(fetchUsage, refreshInterval);
return () => clearInterval(interval);
}, [conversationId, refreshInterval]);
if (loading || !usage) return null;
const getBarColor = () => {
if (usage.warning_level === 'critical') return 'bg-red-500';
if (usage.warning_level === 'approaching') return 'bg-orange-500';
return 'bg-green-500';
};
const getTextColor = () => {
if (usage.warning_level === 'critical') return 'text-red-700';
if (usage.warning_level === 'approaching') return 'text-orange-700';
return 'text-gray-700';
};
return (
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
'use client';
import React, { FC, useEffect, useState } from 'react';
import { ContextUsageResponse } from '@/lib/types/attachment';
import ChatService from '@/services/ChatService';
import { cn } from '@/lib/utils';
import { AlertTriangle } from 'lucide-react';
import {
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger,
} from '@/components/ui/tooltip';
interface ContextUsageIndicatorProps {
conversationId: string;
className?: string;
refreshInterval?: number; // ms
}
export const ContextUsageIndicator: FC<ContextUsageIndicatorProps> = ({
conversationId,
className,
refreshInterval = 30000, // 30 seconds default
}) => {
const [usage, setUsage] = useState<ContextUsageResponse | null>(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
const fetchUsage = async () => {
try {
const data = await ChatService.getContextUsage(conversationId);
setUsage(data);
} catch (error) {
console.error('Error fetching context usage:', error);
} finally {
setLoading(false);
}
};
fetchUsage();
const interval = setInterval(fetchUsage, refreshInterval);
return () => clearInterval(interval);
}, [conversationId, refreshInterval]);
if (loading || !usage) return null;
const getBarColor = () => {
if (usage.warning_level === 'critical') return 'bg-red-500';
if (usage.warning_level === 'approaching') return 'bg-orange-500';
return 'bg-green-500';
};
const getTextColor = () => {
if (usage.warning_level === 'critical') return 'text-red-700';
if (usage.warning_level === 'approaching') return 'text-orange-700';
return 'text-gray-700';
};
return (
<TooltipProvider>
<Tooltip>
<TooltipTrigger asChild>
🤖 Prompt for AI Agents
In components/chat/ContextUsageIndicator.tsx around lines 1 to 61, the file uses
React hooks and must be a client component but is missing the 'use client'
directive; add a single line containing "use client" (with quotes) at the very
top of the file (before any imports) so Next.js treats this as a client
component and the build will succeed.

Comment on lines +27 to +45
useEffect(() => {
const fetchUsage = async () => {
try {
const data = await ChatService.getContextUsage(conversationId);
setUsage(data);
} catch (error) {
console.error('Error fetching context usage:', error);
} finally {
setLoading(false);
}
};

fetchUsage();
const interval = setInterval(fetchUsage, refreshInterval);
return () => clearInterval(interval);
}, [conversationId, refreshInterval]);

if (loading || !usage) return null;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Reset loading state when the conversation changes

When conversationId flips, we keep showing the previous chat’s usage until the next fetch resolves. That briefly surfaces stale context data and can mislead the user. Reset loading to true (and optionally clear usage) before kicking off the new fetch so the indicator disappears until fresh data arrives.

🤖 Prompt for AI Agents
In components/chat/ContextUsageIndicator.tsx around lines 27 to 45, when
conversationId changes we currently keep showing the previous usage until the
new fetch completes; update the useEffect to immediately setLoading(true) (and
optionally setUsage(null)) before calling fetchUsage so the indicator is cleared
while the new data loads; keep the existing fetchUsage, interval setup/teardown,
and error/finally handling but ensure the initial state reset happens at the top
of the effect whenever conversationId or refreshInterval changes.

Comment on lines +1 to +37
import React, { FC } from 'react';
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
} from '@/components/ui/dialog';
import { Button } from '@/components/ui/button';
import { AlertTriangle } from 'lucide-react';
import { ValidationResponse } from '@/lib/types/attachment';
import { formatFileSize } from '@/lib/utils/fileTypes';

interface ValidationErrorModalProps {
open: boolean;
onClose: () => void;
validation: ValidationResponse | null;
fileName: string;
fileSize: number;
onViewAttachments?: () => void;
onChangeModel?: () => void;
}

export const ValidationErrorModal: FC<ValidationErrorModalProps> = ({
open,
onClose,
validation,
fileName,
fileSize,
onViewAttachments,
onChangeModel,
}) => {
if (!validation || validation.can_upload) return null;

return (
<Dialog open={open} onOpenChange={onClose}>
<DialogContent className="max-w-md">

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

Mark the modal as a Client Component

This modal relies on event handlers and UI components meant for the browser. Add 'use client' at the top so Next.js treats it as a Client Component; otherwise the build will flag it when imported.
Apply this diff:

+'use client';
+
 import React, { FC } from 'react';
🤖 Prompt for AI Agents
In components/chat/ValidationErrorModal.tsx lines 1 to 37, the file uses event
handlers and browser-only UI components but is not marked as a Next.js Client
Component; add the directive 'use client' as the very first line of the file so
Next treats it as a client component and the build error is resolved.

Comment thread lib/utils/fileTypes.ts
Comment on lines +62 to +76
export function estimateTokens(file: File): number {
const mimeType = file.type;
const sizeBytes = file.size;

// Server-side heuristics
if (mimeType.includes('pdf')) {
return Math.floor(sizeBytes / 5);
} else if (mimeType.includes('word') || mimeType.includes('document')) {
return Math.floor(sizeBytes / 5);
} else if (mimeType.includes('csv') || mimeType.includes('spreadsheet')) {
return Math.floor(sizeBytes / 6);
} else {
return Math.floor(sizeBytes / 4); // Text/code
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Document the token estimation as a rough approximation.

The estimateTokens function uses rough heuristics (dividing file size by 4-6 depending on type) that may not accurately reflect actual token counts. This should be documented to set proper expectations.

Add JSDoc documentation to clarify this is an approximation:

+/**
+ * Estimates token count for a file using rough heuristics based on file size.
+ * NOTE: This is a rough client-side approximation. Actual token counts
+ * will be calculated server-side and may differ significantly.
+ * 
+ * Heuristics:
+ * - PDF/Word: ~1 token per 5 bytes
+ * - CSV/Spreadsheet: ~1 token per 6 bytes  
+ * - Text/Code: ~1 token per 4 bytes
+ */
 export function estimateTokens(file: File): number {
🤖 Prompt for AI Agents
In lib/utils/fileTypes.ts around lines 62 to 76, the estimateTokens function
uses simple heuristics (dividing file size by 4–6) and lacks documentation
clarifying these are rough approximations; add a JSDoc comment above the
function that states this is an approximate token estimate, explains the
heuristic per MIME type, warns it may diverge from real tokenizer counts, and
suggests using a real tokenizer for accurate counts; keep the comment concise
and include parameter and return descriptions.

Comment thread services/ChatService.ts
Comment on lines +702 to +727
static async validateDocument(
conversationId: string,
file: File
): Promise<ValidationResponse> {
const headers = await getHeaders();
const formData = new FormData();

formData.append('conversation_id', conversationId);
formData.append('file_size', file.size.toString());
formData.append('file_name', file.name);
formData.append('mime_type', file.type);

try {
const response = await axios.post(
`${process.env.NEXT_PUBLIC_CONVERSATION_BASE_URL}/api/v1/media/validate-document`,
formData,
{ headers }
);
return response.data;
} catch (error: any) {
console.error('Document validation error:', error);
throw new Error(
error.response?.data?.detail || 'Failed to validate document'
);
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Fix multipart headers in validateDocument

validateDocument builds FormData, but we’re still forwarding the default headers from getHeaders(), which include Content-Type: application/json. With that header in place the browser won’t set the multipart boundary, so the backend will see the payload as malformed and the validation call fails. In uploadAttachment you already strip the content type for this reason; we need the same treatment here.

   static async validateDocument(
     conversationId: string,
     file: File
   ): Promise<ValidationResponse> {
-    const headers = await getHeaders();
+    const headers = await getHeaders();
+    const formHeaders = { ...headers };
+    delete (formHeaders as Record<string, string>)["Content-Type"];
@@
       const response = await axios.post(
         `${process.env.NEXT_PUBLIC_CONVERSATION_BASE_URL}/api/v1/media/validate-document`,
         formData,
-        { headers }
+        { headers: formHeaders }
       );
📝 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
static async validateDocument(
conversationId: string,
file: File
): Promise<ValidationResponse> {
const headers = await getHeaders();
const formData = new FormData();
formData.append('conversation_id', conversationId);
formData.append('file_size', file.size.toString());
formData.append('file_name', file.name);
formData.append('mime_type', file.type);
try {
const response = await axios.post(
`${process.env.NEXT_PUBLIC_CONVERSATION_BASE_URL}/api/v1/media/validate-document`,
formData,
{ headers }
);
return response.data;
} catch (error: any) {
console.error('Document validation error:', error);
throw new Error(
error.response?.data?.detail || 'Failed to validate document'
);
}
}
static async validateDocument(
conversationId: string,
file: File
): Promise<ValidationResponse> {
const headers = await getHeaders();
const formHeaders = { ...headers };
delete (formHeaders as Record<string, string>)["Content-Type"];
const formData = new FormData();
formData.append('conversation_id', conversationId);
formData.append('file_size', file.size.toString());
formData.append('file_name', file.name);
formData.append('mime_type', file.type);
try {
const response = await axios.post(
`${process.env.NEXT_PUBLIC_CONVERSATION_BASE_URL}/api/v1/media/validate-document`,
formData,
{ headers: formHeaders }
);
return response.data;
} catch (error: any) {
console.error('Document validation error:', error);
throw new Error(
error.response?.data?.detail || 'Failed to validate document'
);
}
}
🤖 Prompt for AI Agents
In services/ChatService.ts around lines 702 to 727, the validateDocument call
builds FormData but passes headers from getHeaders() that include Content-Type:
application/json which prevents the browser/axios from setting the multipart
boundary; remove the Content-Type header before making the POST (e.g., delete
headers['Content-Type'] and delete headers['content-type'] to be safe) and then
call axios.post with the cleaned headers so axios can set the correct multipart
Content-Type with boundary.

@sonarqubecloud

sonarqubecloud Bot commented Dec 9, 2025

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 2

♻️ Duplicate comments (5)
lib/utils/fileTypes.ts (1)

123-137: Use getEffectiveMimeType for consistency.

estimateTokens uses file.type directly, but other functions in this file use getEffectiveMimeType to handle browsers that report empty or generic MIME types. This inconsistency could lead to incorrect token estimates for files where the browser doesn't provide a specific MIME type.

 export function estimateTokens(file: File): number {
-  const mimeType = file.type;
+  const mimeType = getEffectiveMimeType(file);
   const sizeBytes = file.size;

Note: The past review comment about documenting this as a rough approximation is still valid.

components/chat/ContextUsageIndicator.tsx (2)

1-11: Add the 'use client' directive.

This component uses React hooks (useState, useEffect) and must be marked as a client component. Without this directive, Next.js will treat it as a Server Component and the build will fail. As per coding guidelines, use the "use client" directive for client components.

+'use client';
+
 import React, { FC, useEffect, useState } from 'react';

40-55: Reset state when conversationId changes.

When conversationId changes, the component continues showing stale data until the new fetch completes. Reset loading to true and clear usage at the start of the effect.

 useEffect(() => {
+  setLoading(true);
+  setUsage(null);
+
   const fetchUsage = async () => {
services/ChatService.ts (1)

702-727: Fix multipart headers in validateDocument so FormData works

validateDocument builds a FormData body but passes the raw headers from getHeaders(), which almost certainly include Content-Type: application/json. With that header set, axios won’t add the multipart/form-data boundary, and the backend will see a malformed payload. This is the same issue called out in the earlier review and still needs fixing.

You already handle this correctly in uploadAttachment. Mirror that here by cloning and cleaning the headers, deleting both Content-Type and content-type before calling axios.post:

  static async validateDocument(
    conversationId: string,
    file: File
  ): Promise<ValidationResponse> {
-    const headers = await getHeaders();
+    const headers = await getHeaders();
+    const formHeaders: Record<string, string> = { ...(headers as Record<string, string>) };
+    delete formHeaders["Content-Type"];
+    delete formHeaders["content-type"];
@@
-      const response = await axios.post(
+      const response = await axios.post(
         `${process.env.NEXT_PUBLIC_CONVERSATION_BASE_URL}/api/v1/media/validate-document`,
         formData,
-        { headers }
+        { headers: formHeaders }
       );
app/(main)/chat/[chatId]/components/MessageComposer.tsx (1)

119-129: Still need to block send while document validation/upload is in flight

You’ve added documents, validating, uploadingDocument, uploadLocked, etc., but handleSend and the Send button can still fire while a document is being validated or uploaded. In that window, the message is sent without the just-selected document because documents hasn’t been updated with the new attachment yet. This is the same race condition flagged in the previous review.

Consider:

  • Introducing a single flag (e.g. const sendDisabled = isDisabled || validating || uploadingDocument || uploadLocked;) after the relevant useState declarations.
  • Using it for:
    • The send button and ThreadPrimitive.If running={false}:

  •    <ThreadPrimitive.If running={false}>
    
  •    <ThreadPrimitive.If running={false}>
         <button
           type="button"
    
  •        disabled={isDisabled}
    
  •        disabled={sendDisabled}
    

@@

  •        onClick={handleSend}
    
  •        onClick={handleSend}
         >
    
    
    - The composer input’s `submitOnEnter`:
    
    ```diff
    
  •      <ComposerPrimitive.Input
    
  •        submitOnEnter={!isDisabled}
    
  •      <ComposerPrimitive.Input
    
  •        submitOnEnter={!sendDisabled}
    
    
    - An early guard in `handleSend`:
    
    ```diff
    const handleSend = () => {
    
  • // Ensure config is set with current documents before sending
  • if (sendDisabled) {
  •  toast.info("Please wait", {
    
  •    description: "Document validation/upload is still in progress.",
    
  •  });
    
  •  return;
    
  • }
  • // Ensure config is set with current documents before sending
    const documentIds = documents.map(doc => doc.id);
    ```

This keeps the UX consistent: users can’t send until validation/upload has finished, and you avoid messages going out without their intended documents.

Also applies to: 324-345, 621-650, 861-906

🧹 Nitpick comments (7)
lib/utils.ts (1)

153-161: Consider moving imports to the top of the file.

The dayjs imports are placed mid-file after function definitions. Per convention, imports should be grouped at the top of the file for better readability and maintainability. As per coding guidelines, order imports: React/Next imports first, then components, then utilities.

 import { type ClassValue, clsx } from "clsx";
 import { twMerge } from "tailwind-merge";
+import dayjs from "dayjs";
+import utc from "dayjs/plugin/utc";
+import timezone from "dayjs/plugin/timezone";
+import relativeTime from "dayjs/plugin/relativeTime";
+
+dayjs.extend(utc);
+dayjs.extend(timezone);
+dayjs.extend(relativeTime);

 export function cn(...inputs: ClassValue[]) {

Then remove the duplicate imports from lines 153-161.

components/chat/ContextUsageIndicator.tsx (1)

140-154: Extract nested ternaries for readability.

The nested ternary operations flagged by SonarCloud reduce readability. Consider extracting to a helper function.

+const getHeaderBgClass = (warningLevel: string) => {
+  if (warningLevel === 'critical') return 'bg-red-50';
+  if (warningLevel === 'approaching') return 'bg-amber-50';
+  return 'bg-gray-50';
+};
+
+const getWarningTextClass = (warningLevel: string) => {
+  if (warningLevel === 'critical') return 'text-red-600';
+  if (warningLevel === 'approaching') return 'text-amber-600';
+  return 'text-gray-600';
+};

Then use getHeaderBgClass(usage.warning_level) at line 142-144 and getWarningTextClass(usage.warning_level) at lines 149-150.

app/(main)/chat/[chatId]/page.tsx (1)

130-134: Consider inverting the condition for clarity.

Per SonarCloud, negated conditions can reduce readability. Consider swapping the branches.

-      if (!list_system_agents.includes(agentId)) {
-        setChatAccess(info.access_type);
-      } else {
+      if (list_system_agents.includes(agentId)) {
         setChatAccess(info.is_creator ? "write" : info.access_type);
+      } else {
+        setChatAccess(info.access_type);
       }
services/ChatService.ts (3)

169-205: AttachmentIds default param ordering and noisy logging in streamMessage

  • Sonar is right that having defaulted params (images = [], attachmentIds: string[] = []) before a required callback and sessionId makes the signature harder to read and easier to misuse. Consider either moving these defaulted params after sessionId or making them optional without defaults to align with the “default parameters last” rule.
  • The console.log/console.warn calls around attachmentIds will generate a lot of noise in production; it’s worth either gating them behind a debug flag or removing them once you’re confident in the flow.

729-790: Harden upload headers and handle metadata failures more gracefully

Two minor but worthwhile robustness points here:

  • For uploadAttachment, you only delete 'Content-Type' from uploadHeaders. If getHeaders() ever returns lowercase 'content-type', the stale header will remain and can break multipart uploads. It’s safer to delete both keys:
      const uploadHeaders = { ...headers };
-      delete (uploadHeaders as any)['Content-Type'];
+      delete (uploadHeaders as any)['Content-Type'];
+      delete (uploadHeaders as any)['content-type'];
  • If getAttachmentInfo fails after a successful upload, you currently treat it as “Upload failed” and drop the attachment entirely. That can create “ghost” uploads on the backend and forces the user to repeat the upload. Consider:
    • catching metadata failures separately,
    • still adding an attachment entry based on uploadResult (with token_count / metadata undefined or defaulted), and
    • surfacing a softer toast like “Uploaded, but token details are unavailable right now.”

This would give a better UX and avoid wasted uploads while still signaling that enrichment failed.


813-843: Make downloadAttachment browser-safe and address Sonar’s global/window warnings

downloadAttachment assumes a browser environment (window.URL, document.body.removeChild), and Sonar is flagging both window usage and removeChild. You can both guard this as browser-only and satisfy the linter:

  static async downloadAttachment(
    attachmentId: string,
    fileName: string
  ): Promise<void> {
-    const headers = await getHeaders();
-
-    try {
-      const response = await axios.get(
+    const headers = await getHeaders();
+
+    // Guard against accidental server-side usage
+    if (typeof window === "undefined" || typeof document === "undefined") {
+      throw new Error("downloadAttachment can only be used in a browser environment");
+    }
+
+    try {
+      const response = await axios.get(
         `${process.env.NEXT_PUBLIC_CONVERSATION_BASE_URL}/api/v1/media/${attachmentId}/download`,
         {
           headers,
           responseType: 'blob'
         }
       );
 
-      // Create download link
-      const url = window.URL.createObjectURL(response.data);
+      // Create download link
+      const url = globalThis.URL.createObjectURL(response.data as Blob);
       const a = document.createElement('a');
       a.href = url;
       a.download = fileName;
       document.body.appendChild(a);
       a.click();
-      window.URL.revokeObjectURL(url);
-      document.body.removeChild(a);
+      globalThis.URL.revokeObjectURL(url);
+      a.remove();

That keeps this helper clearly client-only and aligns with the static analysis recommendations.

app/(main)/chat/[chatId]/components/MessageComposer.tsx (1)

49-58: Remove unused file-type utilities to keep imports clean

isDocumentType, isImageType, and detectFileTypeByExtension are imported from @/lib/utils/fileTypes but never used in this component. Sonar is already flagging these; dropping them will clean up the file and satisfy the analyzer:

-import {
-  isDocumentType,
-  isImageType,
-  isDocumentTypeByFile,
-  isImageTypeByFile,
-  detectFileTypeByExtension,
-  MAX_FILE_SIZE,
-  getSupportedFileExtensions
-} from "@/lib/utils/fileTypes";
+import {
+  isDocumentTypeByFile,
+  isImageTypeByFile,
+  MAX_FILE_SIZE,
+  getSupportedFileExtensions
+} from "@/lib/utils/fileTypes";
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 772e4cb and 8e7279c.

📒 Files selected for processing (7)
  • app/(main)/chat/[chatId]/components/MessageComposer.tsx (11 hunks)
  • app/(main)/chat/[chatId]/page.tsx (2 hunks)
  • components/chat/ContextUsageIndicator.tsx (1 hunks)
  • components/chat/DocumentAttachmentCard.tsx (1 hunks)
  • lib/utils.ts (2 hunks)
  • lib/utils/fileTypes.ts (1 hunks)
  • services/ChatService.ts (4 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.{ts,tsx}

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.{ts,tsx}: Use PascalCase for component names and camelCase for utility names
Order imports: React/Next imports first, then components, then utilities
Use Next.js error boundaries, optional chaining, and nullish coalescing for error handling

Files:

  • lib/utils.ts
  • services/ChatService.ts
  • components/chat/ContextUsageIndicator.tsx
  • app/(main)/chat/[chatId]/page.tsx
  • lib/utils/fileTypes.ts
  • app/(main)/chat/[chatId]/components/MessageComposer.tsx
  • components/chat/DocumentAttachmentCard.tsx
**/*.tsx

📄 CodeRabbit inference engine (CLAUDE.md)

**/*.tsx: Use Tailwind CSS for styling and the cn utility for conditional classes
Use functional components with TypeScript FC types
Prefer destructuring props at the component level
Use the "use client" directive for client components

Files:

  • components/chat/ContextUsageIndicator.tsx
  • app/(main)/chat/[chatId]/page.tsx
  • app/(main)/chat/[chatId]/components/MessageComposer.tsx
  • components/chat/DocumentAttachmentCard.tsx
🧠 Learnings (2)
📚 Learning: 2025-07-22T09:20:21.427Z
Learnt from: CR
Repo: potpie-ai/potpie-ui PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-07-22T09:20:21.427Z
Learning: Applies to **/*.tsx : Use the "use client" directive for client components

Applied to files:

  • components/chat/ContextUsageIndicator.tsx
📚 Learning: 2025-07-22T09:20:21.427Z
Learnt from: CR
Repo: potpie-ai/potpie-ui PR: 0
File: CLAUDE.md:0-0
Timestamp: 2025-07-22T09:20:21.427Z
Learning: Applies to **/*.{ts,tsx} : Use Next.js error boundaries, optional chaining, and nullish coalescing for error handling

Applied to files:

  • components/chat/ContextUsageIndicator.tsx
🧬 Code graph analysis (2)
services/ChatService.ts (1)
lib/types/attachment.ts (4)
  • ValidationResponse (4-18)
  • AttachmentUploadResponse (20-26)
  • AttachmentInfo (51-60)
  • ContextUsageResponse (62-76)
components/chat/DocumentAttachmentCard.tsx (3)
lib/types/attachment.ts (1)
  • DocumentAttachment (79-83)
lib/utils/fileTypes.ts (2)
  • getFileTypeInfo (107-115)
  • formatFileSize (117-121)
lib/utils.ts (1)
  • cn (4-6)
🪛 GitHub Check: SonarCloud Code Analysis
services/ChatService.ts

[warning] 169-169: Default parameters should be last.

See more on https://sonarcloud.io/project/issues?id=getmomentum_potpie-ui&issues=AZsB0h21ZnGe8h3MzMDW&open=AZsB0h21ZnGe8h3MzMDW&pullRequest=231


[warning] 829-829: Prefer globalThis over window.

See more on https://sonarcloud.io/project/issues?id=getmomentum_potpie-ui&issues=AZsB0h21ZnGe8h3MzMDc&open=AZsB0h21ZnGe8h3MzMDc&pullRequest=231


[warning] 835-835: Prefer globalThis over window.

See more on https://sonarcloud.io/project/issues?id=getmomentum_potpie-ui&issues=AZsB0h21ZnGe8h3MzMDd&open=AZsB0h21ZnGe8h3MzMDd&pullRequest=231


[warning] 836-836: Prefer childNode.remove() over parentNode.removeChild(childNode).

See more on https://sonarcloud.io/project/issues?id=getmomentum_potpie-ui&issues=AZsB0h21ZnGe8h3MzMDe&open=AZsB0h21ZnGe8h3MzMDe&pullRequest=231

components/chat/ContextUsageIndicator.tsx

[warning] 150-150: Extract this nested ternary operation into an independent statement.

See more on https://sonarcloud.io/project/issues?id=getmomentum_potpie-ui&issues=AZsB0h1zZnGe8h3MzMDT&open=AZsB0h1zZnGe8h3MzMDT&pullRequest=231


[warning] 143-143: Extract this nested ternary operation into an independent statement.

See more on https://sonarcloud.io/project/issues?id=getmomentum_potpie-ui&issues=AZsB0h1zZnGe8h3MzMDS&open=AZsB0h1zZnGe8h3MzMDS&pullRequest=231

app/(main)/chat/[chatId]/page.tsx

[warning] 130-130: Unexpected negated condition.

See more on https://sonarcloud.io/project/issues?id=getmomentum_potpie-ui&issues=AZsB0hwKZnGe8h3MzMDI&open=AZsB0hwKZnGe8h3MzMDI&pullRequest=231

app/(main)/chat/[chatId]/components/MessageComposer.tsx

[warning] 51-51: Remove this unused import of 'isDocumentType'.

See more on https://sonarcloud.io/project/issues?id=getmomentum_potpie-ui&issues=AZsB0h0BZnGe8h3MzMDM&open=AZsB0h0BZnGe8h3MzMDM&pullRequest=231


[warning] 55-55: Remove this unused import of 'detectFileTypeByExtension'.

See more on https://sonarcloud.io/project/issues?id=getmomentum_potpie-ui&issues=AZsB0h0BZnGe8h3MzMDO&open=AZsB0h0BZnGe8h3MzMDO&pullRequest=231


[warning] 52-52: Remove this unused import of 'isImageType'.

See more on https://sonarcloud.io/project/issues?id=getmomentum_potpie-ui&issues=AZsB0h0BZnGe8h3MzMDN&open=AZsB0h0BZnGe8h3MzMDN&pullRequest=231

components/chat/DocumentAttachmentCard.tsx

[warning] 2-2: Remove this unused import of 'FileText'.

See more on https://sonarcloud.io/project/issues?id=getmomentum_potpie-ui&issues=AZsB0h1oZnGe8h3MzMDR&open=AZsB0h1oZnGe8h3MzMDR&pullRequest=231

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Build, Deploy, and Comment
🔇 Additional comments (5)
lib/utils.ts (1)

95-149: LGTM!

The expanded mock user object correctly includes the Firebase internal methods (_stopProactiveRefresh, _startProactiveRefresh, stsTokenManager, proactiveRefresh) needed for auth state management compatibility.

lib/utils/fileTypes.ts (1)

46-81: Several extensions map to MIME types not in SUPPORTED_DOCUMENT_TYPES.

Extensions like .rb, .php, .c, .cpp, .h, .cs, .swift, .kt, .scala, .sh, .sql, .html, .css, .scss, .less, .vue, .svelte map to MIME types that aren't in SUPPORTED_DOCUMENT_TYPES. This means isDocumentTypeByFile will return false for these files even though extension detection works. Either add these MIME types to SUPPORTED_DOCUMENT_TYPES or document this as intentional behavior.

components/chat/DocumentAttachmentCard.tsx (1)

15-91: LGTM!

Well-structured component with:

  • Proper TypeScript FC typing and prop destructuring
  • Good accessibility with aria-label attributes on interactive elements
  • Tailwind CSS with cn utility for conditional classes
  • Clean handling of optional metadata (token_count, page_count, row_count)
app/(main)/chat/[chatId]/page.tsx (2)

115-128: LGTM!

Good defensive programming with optional chaining and early return. The error handling provides clear user feedback via toast and sets a structured error state for the UI.


150-154: Good defensive guard for creator_id.

Adding the info.creator_id check prevents unnecessary API calls when the creator ID is missing.

Comment on lines +1 to +5
import React, { FC } from 'react';
import { X, Download, FileText } from 'lucide-react';
import { DocumentAttachment } from '@/lib/types/attachment';
import { formatFileSize, getFileTypeInfo } from '@/lib/utils/fileTypes';
import { cn } from '@/lib/utils';

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Add 'use client' directive and remove unused import.

This component should be marked as a client component per coding guidelines. Also, FileText is imported but never used (per SonarCloud).

+'use client';
+
 import React, { FC } from 'react';
-import { X, Download, FileText } from 'lucide-react';
+import { X, Download } from 'lucide-react';
 import { DocumentAttachment } from '@/lib/types/attachment';
📝 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
import React, { FC } from 'react';
import { X, Download, FileText } from 'lucide-react';
import { DocumentAttachment } from '@/lib/types/attachment';
import { formatFileSize, getFileTypeInfo } from '@/lib/utils/fileTypes';
import { cn } from '@/lib/utils';
'use client';
import React, { FC } from 'react';
import { X, Download } from 'lucide-react';
import { DocumentAttachment } from '@/lib/types/attachment';
import { formatFileSize, getFileTypeInfo } from '@/lib/utils/fileTypes';
import { cn } from '@/lib/utils';
🧰 Tools
🪛 GitHub Check: SonarCloud Code Analysis

[warning] 2-2: Remove this unused import of 'FileText'.

See more on https://sonarcloud.io/project/issues?id=getmomentum_potpie-ui&issues=AZsB0h1oZnGe8h3MzMDR&open=AZsB0h1oZnGe8h3MzMDR&pullRequest=231

🤖 Prompt for AI Agents
In components/chat/DocumentAttachmentCard.tsx lines 1-5, add the React client
directive and remove the unused FileText import: put a single line "'use
client'" at the very top of the file (before any imports) and update the
lucide-react import to drop FileText (leave X and Download), ensuring no unused
imports remain.

Comment thread lib/utils/fileTypes.ts
Comment on lines +83 to +86
export function detectFileTypeByExtension(fileName: string): string | null {
const ext = '.' + fileName.split('.').pop()?.toLowerCase();
return EXTENSION_TO_MIME[ext] || null;
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Handle filenames without extensions.

If the filename has no dot (e.g., "README"), split('.').pop() returns "README", and prepending '.' creates ".README", which won't match any extension in the map. Consider adding a guard.

 export function detectFileTypeByExtension(fileName: string): string | null {
-  const ext = '.' + fileName.split('.').pop()?.toLowerCase();
+  const parts = fileName.split('.');
+  if (parts.length < 2) return null;
+  const ext = '.' + parts.pop()?.toLowerCase();
   return EXTENSION_TO_MIME[ext] || null;
 }
📝 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 function detectFileTypeByExtension(fileName: string): string | null {
const ext = '.' + fileName.split('.').pop()?.toLowerCase();
return EXTENSION_TO_MIME[ext] || null;
}
export function detectFileTypeByExtension(fileName: string): string | null {
const parts = fileName.split('.');
if (parts.length < 2) return null;
const ext = '.' + parts.pop()?.toLowerCase();
return EXTENSION_TO_MIME[ext] || null;
}
🤖 Prompt for AI Agents
In lib/utils/fileTypes.ts around lines 83 to 86, the current logic treats names
without dots (e.g., "README") as if their entire name were an extension; change
it to first check for a real extension by finding the last '.' and ensuring it's
not the leading dot of a hidden file (require lastDot > 0). If no valid dot is
found return null; otherwise compute ext = fileName.slice(lastDot).toLowerCase()
and return EXTENSION_TO_MIME[ext] || null.

@sonarqubecloud

Copy link
Copy Markdown

Quality Gate Failed Quality Gate failed

Failed conditions
C Reliability Rating on New Code (required ≥ A)

See analysis details on SonarQube Cloud

Catch issues before they fail your Quality Gate with our IDE extension SonarQube for IDE

@github-actions

Copy link
Copy Markdown

Cloud Run service deployed: https://pr-doc-cj6r7x3fpa-uc.a.run.app

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (4)
lib/utils/emailValidation.ts (1)

268-275: Consider masking PII in error logs.

Logging userId and email in error details could raise compliance concerns (GDPR/CCPA). Consider masking or omitting PII from logs, or ensuring these logs are not persisted in external monitoring systems that may retain data beyond compliance requirements.

🛡️ Suggested approach to mask PII
     const errorDetails = {
       message: 'Failed to delete Firebase user account',
       error: error?.message || String(error),
       errorCode: error?.code,
-      userId: user?.uid,
-      email: user?.email,
+      userId: user?.uid ? `${user.uid.substring(0, 4)}...` : undefined,
+      email: user?.email ? `${user.email.split('@')[0]?.substring(0, 2)}...@${user.email.split('@')[1]}` : undefined,
       stack: error?.stack,
     };
app/(auth)/link-github/page.tsx (3)

32-32: Analytics event fires on every render, not on user action.

posthog.capture("github login clicked") is called directly in the component body, so it executes on mount and every re-render—not when the user actually clicks. This will produce inflated/misleading analytics.

Move it inside onGithub() to capture the actual click, or wrap in useEffect with an empty dependency array if you want to track page views.

🐛 Proposed fix

If tracking the click action:

  const onGithub = async () => {
    try {
+     posthog.capture("github login clicked");
      // Capture the currently signed-in user...

And remove line 32.

Or if tracking page views:

+ import React, { useRef, useEffect } from "react";
...
  const router = useRouter();
  const popupRef = useRef<Window | null>(null);
- posthog.capture("github login clicked");
+ useEffect(() => {
+   posthog.capture("link github page viewed");
+ }, [posthog]);

69-111: Success toast fires before the API call completes.

userSignup is a promise that's never awaited—toast.success on line 109 executes immediately after initiating the request, not after it succeeds. Users see "Account created successfully" even if the request subsequently fails.

🐛 Proposed fix

Move the success toast inside the .then() block:

        .then((res: { data: any }) => {
+         toast.success(
+           "Account created successfully as " + result.user.displayName
+         );
          openPopup();
          router.push(`/newchat`);
          return res.data;
        })
        .catch((e: any) => {
          // Check for 409 Conflict (GitHub already linked)
          if (e?.response?.status === 409 && e?.response?.data?.error) {
            toast.error(e.response.data.error);
          } else if (e?.response?.data?.error) {
            toast.error(e.response.data.error);
          } else {
-         toast.error("Signup call unsuccessful");
+           toast.error("Signup call unsuccessful");
          }
        });
-     toast.success(
-       "Account created successfully as  " + result.user.displayName
-     );

89-90: Relying on internal Firebase _tokenResponse property.

Accessing _tokenResponse (underscore-prefixed internal property) via as any bypasses type safety and may break on Firebase updates without warning. Use the official API instead:

      const result = await linkWithPopup(originalUser, provider);
+     const credential = GithubAuthProvider.credentialFromResult(result);
+     const accessToken = credential?.accessToken;
...
-         accessToken: (result as any)._tokenResponse.oauthAccessToken,
-         providerUsername: (result as any)._tokenResponse.screenName,
+         accessToken: accessToken,
+         providerUsername: result.user.providerData.find(p => p.providerId === "github.com")?.displayName || "",

Note: If the screenName value is essential and not available from providerData, consider fetching it via GitHub's API (GET /user) using the access token obtained above.

🤖 Fix all issues with AI agents
In `@app/`(main)/chat/[chatId]/components/MessageComposer.tsx:
- Around line 576-592: handleEnhancePrompt currently overwrites the user's
message with "Enhancing..." before calling ChatService.enhancePrompt, so if the
API fails the original text is lost; fix it by capturing the current message
into a local variable (e.g., originalMessage) before calling
setMessage/composer.setText, set the UI to the enhancing state using that local
save, and in the catch block restore the original text via
setMessage(originalMessage) and composer.setText(originalMessage) (keep
setIsEnhancing(false) in finally). Reference: handleEnhancePrompt, setMessage,
composer.setText, ChatService.enhancePrompt, setIsEnhancing.

In `@app/`(main)/chat/[chatId]/components/Thread.tsx:
- Around line 386-419: In the documentAttachments map inside the Thread
component (the JSX block mapping documentAttachments in Thread.tsx), replace the
React key currently using the array index with a stable unique identifier from
the attachment object (use attachment.id or another unique id field) so each
mapped <div> uses key={attachment.id} instead of key={index}; ensure you handle
the case where id might be missing by falling back to a stable unique value if
necessary.
🧹 Nitpick comments (10)
components/Layouts/minors/nav-user.tsx (1)

123-130: Consider adding accessibility attributes for screen reader support.

The title attribute provides a native tooltip, but screen readers may not consistently announce it. For improved accessibility, consider adding aria-label and role="img" to the wrapper spans.

♿ Optional accessibility enhancement
                   {user.emailVerified ? (
-                    <span title="Email verified">
+                    <span title="Email verified" role="img" aria-label="Email verified">
                       <CheckCircle2 className="h-3 w-3 text-green-500 flex-shrink-0" />
                     </span>
                   ) : (
-                    <span title="Email not verified">
+                    <span title="Email not verified" role="img" aria-label="Email not verified">
                       <AlertCircle className="h-3 w-3 text-amber-500 flex-shrink-0" />
                     </span>
                   )}
services/ChatService.ts (2)

328-335: Remove debug console statements before merging.

These console.log and console.warn calls appear to be debug artifacts. The warning on line 334 is particularly noisy since most messages won't have attachments.

🧹 Proposed fix to remove debug logs
       // Add attachment IDs if present
-      console.log('[ChatService] streamMessage - attachmentIds:', attachmentIds);
       if (attachmentIds.length > 0) {
-        console.log('[ChatService] streamMessage - Adding attachment_ids to FormData:', JSON.stringify(attachmentIds));
         formData.append('attachment_ids', JSON.stringify(attachmentIds));
-      } else {
-        console.warn('[ChatService] streamMessage - No attachment IDs to send');
       }

1068-1076: Consider using try-finally for cleanup robustness.

If an error occurs after createObjectURL but before cleanup, the blob URL won't be revoked. A try-finally ensures cleanup runs regardless of exceptions.

♻️ Proposed improvement for cleanup
       // Create download link
       const url = window.URL.createObjectURL(response.data);
       const a = document.createElement('a');
-      a.href = url;
-      a.download = fileName;
-      document.body.appendChild(a);
-      a.click();
-      window.URL.revokeObjectURL(url);
-      document.body.removeChild(a);
+      try {
+        a.href = url;
+        a.download = fileName;
+        document.body.appendChild(a);
+        a.click();
+      } finally {
+        window.URL.revokeObjectURL(url);
+        if (a.parentNode) {
+          document.body.removeChild(a);
+        }
+      }
app/(main)/newchat/components/step1.tsx (1)

213-231: Side effect in queryFn may cause unexpected behavior on refetches.

Dispatching Redux actions inside queryFn is discouraged because React Query may refetch this query (on window focus, stale time, manual refetch, etc.). If the user manually selects a different repository after the initial load, a background refetch could reset their selection back to defaultRepo.

Consider moving this logic to a useEffect that watches UserRepositorys and only runs once on initial data load:

♻️ Suggested refactor using useEffect
  const { data: UserRepositorys, isLoading: UserRepositorysLoading } = useQuery(
    {
      queryKey: ["user-repository"],
-     queryFn: async () => {
-       const repos =
-         await BranchAndRepositoryService.getUserRepositories().then((data) => {
-           if (defaultRepo && data.length > 0) {
-             const decodedDefaultRepo =
-               decodeURIComponent(defaultRepo).toLowerCase();
-             const matchingRepo = data.find((repo: RepoIdentifier) => {
-               const repoIdentifier = getRepoIdentifier(repo);
-               return (
-                 repoIdentifier &&
-                 repoIdentifier.toLowerCase() === decodedDefaultRepo
-               );
-             });
-             dispatch(
-               setRepoName(matchingRepo ? decodeURIComponent(defaultRepo) : "")
-             );
-           }
-           return data;
-         });
-       return repos;
-     },
+     queryFn: () => BranchAndRepositoryService.getUserRepositories(),
    }
  );

Then add a separate effect:

// Set default repo from URL params on initial data load
useEffect(() => {
  if (UserRepositorys && defaultRepo && !repoName) {
    const decodedDefaultRepo = decodeURIComponent(defaultRepo).toLowerCase();
    const matchingRepo = UserRepositorys.find((repo: RepoIdentifier) => {
      const repoIdentifier = getRepoIdentifier(repo);
      return repoIdentifier && repoIdentifier.toLowerCase() === decodedDefaultRepo;
    });
    if (matchingRepo) {
      dispatch(setRepoName(decodeURIComponent(defaultRepo)));
    }
  }
}, [UserRepositorys, defaultRepo]);
lib/utils/emailValidation.ts (1)

8-11: Use Firebase's getAdditionalUserInfo() helper instead of type casting.

Firebase provides an official getAdditionalUserInfo(userCredential) function that returns properly typed AdditionalUserInfo | null, eliminating the need for custom type definitions and unsafe casting.

♻️ Suggested refactor using Firebase's built-in helper
-// Firebase modular UserCredential type omits additionalUserInfo; it exists at runtime from signInWithPopup
-type UserCredentialWithAdditionalInfo = UserCredential & {
-  additionalUserInfo?: { isNewUser?: boolean } | null;
-};

Add import:

+import { getAdditionalUserInfo } from 'firebase/auth';

Then in isNewUser (lines 198-202):

-  const cred = result as UserCredentialWithAdditionalInfo;
   // Method 1: additionalUserInfo.isNewUser
   // Note: This only returns true if the current SDK call created the account.
   // It will be false for accounts created via Admin SDK, console, or backend.
-  if (cred.additionalUserInfo?.isNewUser === true) {
+  const additionalInfo = getAdditionalUserInfo(result);
+  if (additionalInfo?.isNewUser === true) {
     return true;
   }
app/(auth)/link-github/page.tsx (2)

5-17: Unused imports can be removed.

Several imports are not used in this component:

  • From @/public: arrowcon, chat, cloud, cross, logo60, sendBlue, setting
  • From lucide-react: Link

Removing these will reduce bundle size and improve code clarity.

♻️ Proposed cleanup
-import {
-  arrowcon,
-  chat,
-  cloud,
-  cross,
-  logo60,
-  logoWithText,
-  sendBlue,
-  setting,
-} from "@/public";
+import { logoWithText } from "@/public";
...
-import { Link, LucideCheck, LucideGithub } from "lucide-react";
+import { LucideCheck, LucideGithub } from "lucide-react";

1-22: Import order doesn't follow guidelines.

Per coding guidelines, imports should be ordered: React/Next imports first, then components, then utilities. Currently React is at line 21 and Next imports are at lines 18-19.

♻️ Suggested import order
"use client";
import React, { useRef } from "react";
import Image from "next/image";
import { useRouter } from "next/navigation";

import { GithubAuthProvider, linkWithPopup } from "firebase/auth";
import axios from "axios";
import { usePostHog } from "posthog-js/react";
import { LucideCheck, LucideGithub } from "lucide-react";
import { toast } from "sonner";

import { Button } from "@/components/ui/button";
import { auth } from "@/configs/Firebase-config";
import { logoWithText } from "@/public";
import getHeaders from "@/app/utils/headers.util";

As per coding guidelines: "Order imports: React/Next imports first, then components, then utilities"

app/(main)/chat/[chatId]/components/MessageComposer.tsx (1)

469-478: Consider specific error type checking instead of any

The error handling relies on error.name checks with an any type. Consider using a type guard or checking for axios-specific cancellation to improve type safety.

♻️ Suggested improvement
-      } catch (error: any) {
-        if (error.name === 'AbortError' || error.name === 'CanceledError') {
+      } catch (error: unknown) {
+        if (
+          error instanceof Error &&
+          (error.name === 'AbortError' || error.name === 'CanceledError')
+        ) {
           // User cancelled, already handled
           return;
         }
         console.error("Document upload error:", error);
         toast.error("Upload failed", {
           description:
-            error.message || "Failed to upload document. Please try again.",
+            error instanceof Error
+              ? error.message
+              : "Failed to upload document. Please try again.",
         });
app/(main)/chat/[chatId]/components/Thread.tsx (2)

345-346: Consider defining a typed interface for message metadata

The any cast on message.metadata bypasses type checking. Consider defining a proper interface for the custom metadata structure.

♻️ Suggested improvement
// At the top of the file or in a shared types file
interface MessageMetadata {
  custom?: {
    documentAttachments?: DocumentAttachment[];
    selectedNodes?: unknown[];
    documentIds?: string[];
  };
}

// In the component
const metadataDocumentAttachments =
  (message.metadata as MessageMetadata | undefined)?.custom?.documentAttachments ?? [];

396-401: Add fallback for unknown attachment types

The icon display only handles specific attachment_type values. Unknown types will render an empty span. Consider adding a default fallback icon.

♻️ Suggested improvement
                   <span className="text-lg">
                     {attachment.attachment_type === "pdf" && "📄"}
                     {attachment.attachment_type === "spreadsheet" && "📊"}
                     {attachment.attachment_type === "code" && "💻"}
                     {attachment.attachment_type === "document" && "📝"}
+                    {!["pdf", "spreadsheet", "code", "document"].includes(
+                      attachment.attachment_type
+                    ) && "📎"}
                   </span>

Comment on lines +576 to +592
const handleEnhancePrompt = async () => {
try {
setIsEnhancing(true);
setMessage("Enhancing...");
composer.setText("Enhancing...");
const enhancedMessage = await ChatService.enhancePrompt(
conversation_id,
message
);
setMessage(enhancedMessage);
composer.setText(enhancedMessage);
} catch (error) {
console.error("Error enhancing prompt:", error);
} finally {
setIsEnhancing(false);
}
};

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Original message is lost if prompt enhancement fails

When handleEnhancePrompt is called, the message is immediately overwritten with "Enhancing...". If the API call fails, the original message is not restored, causing user data loss.

🐛 Proposed fix to preserve original message
 const handleEnhancePrompt = async () => {
+  const originalMessage = message;
   try {
     setIsEnhancing(true);
     setMessage("Enhancing...");
     composer.setText("Enhancing...");
     const enhancedMessage = await ChatService.enhancePrompt(
       conversation_id,
-      message
+      originalMessage
     );
     setMessage(enhancedMessage);
     composer.setText(enhancedMessage);
   } catch (error) {
     console.error("Error enhancing prompt:", error);
+    // Restore original message on failure
+    setMessage(originalMessage);
+    composer.setText(originalMessage);
+    toast.error("Failed to enhance prompt");
   } finally {
     setIsEnhancing(false);
   }
 };
🤖 Prompt for AI Agents
In `@app/`(main)/chat/[chatId]/components/MessageComposer.tsx around lines 576 -
592, handleEnhancePrompt currently overwrites the user's message with
"Enhancing..." before calling ChatService.enhancePrompt, so if the API fails the
original text is lost; fix it by capturing the current message into a local
variable (e.g., originalMessage) before calling setMessage/composer.setText, set
the UI to the enhancing state using that local save, and in the catch block
restore the original text via setMessage(originalMessage) and
composer.setText(originalMessage) (keep setIsEnhancing(false) in finally).
Reference: handleEnhancePrompt, setMessage, composer.setText,
ChatService.enhancePrompt, setIsEnhancing.

Comment on lines +386 to +419
{documentAttachments.map((attachment: any, index: number) => {
const tokenCount =
attachment.token_count || attachment.file_metadata?.token_count;
const metadata = attachment.metadata || attachment.file_metadata;

return (
<div
key={index}
className="flex items-center gap-2 text-xs bg-white rounded-lg p-2 border"
>
<span className="text-lg">
{attachment.attachment_type === "pdf" && "📄"}
{attachment.attachment_type === "spreadsheet" && "📊"}
{attachment.attachment_type === "code" && "💻"}
{attachment.attachment_type === "document" && "📝"}
</span>
<div className="flex-1 min-w-0">
<div className="font-medium truncate">
{attachment.file_name}
</div>
{tokenCount && (
<div className="text-gray-500">
{tokenCount.toLocaleString()} tokens
</div>
)}
{metadata?.page_count && (
<div className="text-gray-400 text-xs">
{metadata.page_count} pages
</div>
)}
</div>
</div>
);
})}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Use unique id instead of array index for document attachment keys

Using array index as the React key can cause rendering issues if documents are removed or reordered. Document attachments have unique id properties that should be used instead.

🐛 Proposed fix
             {documentAttachments.map((attachment: any, index: number) => {
               const tokenCount =
                 attachment.token_count || attachment.file_metadata?.token_count;
               const metadata = attachment.metadata || attachment.file_metadata;

               return (
                 <div
-                  key={index}
+                  key={attachment.id || index}
                   className="flex items-center gap-2 text-xs bg-white rounded-lg p-2 border"
                 >
🤖 Prompt for AI Agents
In `@app/`(main)/chat/[chatId]/components/Thread.tsx around lines 386 - 419, In
the documentAttachments map inside the Thread component (the JSX block mapping
documentAttachments in Thread.tsx), replace the React key currently using the
array index with a stable unique identifier from the attachment object (use
attachment.id or another unique id field) so each mapped <div> uses
key={attachment.id} instead of key={index}; ensure you handle the case where id
might be missing by falling back to a stable unique value if necessary.

Comment on lines +55 to +61
const credential = GithubAuthProvider.credentialFromResult(result);
const accessToken =
credential?.accessToken ??
(result as any)?._tokenResponse?.oauthAccessToken ??
null;

const providerUsername = (result as any)?._tokenResponse?.screenName;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

does oauth changes required for documents support?

<CommandSeparator className="my-1" />
</>
)}
{process.env.NEXT_PUBLIC_BASE_URL?.includes("localhost") && (

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

cannot add public depo in localhost?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants