diff --git a/frontend/src/components/ComparisonChart.tsx b/frontend/src/components/ComparisonChart.tsx index e9176c924..d13187aa9 100644 --- a/frontend/src/components/ComparisonChart.tsx +++ b/frontend/src/components/ComparisonChart.tsx @@ -169,13 +169,11 @@ export function ComparisonChart() { {/* Header Row */}
-
- -
+
{products.map((product) => (
diff --git a/frontend/src/components/ContextLimitDialog.tsx b/frontend/src/components/ContextLimitDialog.tsx new file mode 100644 index 000000000..3ed7b32ae --- /dev/null +++ b/frontend/src/components/ContextLimitDialog.tsx @@ -0,0 +1,80 @@ +import { + Dialog, + DialogContent, + DialogDescription, + DialogFooter, + DialogHeader, + DialogTitle +} from "@/components/ui/dialog"; +import { Button } from "@/components/ui/button"; +import { AlertCircle, MessageCircle } from "lucide-react"; + +interface ContextLimitDialogProps { + open: boolean; + onOpenChange: (open: boolean) => void; + currentModel?: string; + hasDocument?: boolean; +} + +export function ContextLimitDialog({ + open, + onOpenChange, + currentModel, + hasDocument +}: ContextLimitDialogProps) { + const isGemma = currentModel?.includes("gemma"); + + return ( + + + +
+
+ +
+ Message Too Large +
+ + Your message exceeds the context limit for the current model. + +
+ +
+
+

Here's what you can try:

+
    +
  • + + + Shorten your message - Try reducing the amount of text or content + +
  • + {hasDocument && ( +
  • + + + Use a smaller document - Try uploading a shorter document or + extracting only the relevant sections + +
  • + )} + {isGemma && ( +
  • + + + Switch to a model with more context - Try DeepSeek R1, Mistral, + or other models that support 128k tokens + +
  • + )} +
+
+
+ + + + +
+
+ ); +} diff --git a/frontend/src/components/UnifiedChat.tsx b/frontend/src/components/UnifiedChat.tsx index fb7714e11..fd174bae9 100644 --- a/frontend/src/components/UnifiedChat.tsx +++ b/frontend/src/components/UnifiedChat.tsx @@ -50,6 +50,7 @@ import { useLocalState } from "@/state/useLocalState"; import { useOpenSecret } from "@opensecret/react"; import { UpgradePromptDialog } from "@/components/UpgradePromptDialog"; import { DocumentPlatformDialog } from "@/components/DocumentPlatformDialog"; +import { ContextLimitDialog } from "@/components/ContextLimitDialog"; import { RecordingOverlay } from "@/components/RecordingOverlay"; import { Alert, AlertDescription } from "@/components/ui/alert"; import { AlertCircle } from "lucide-react"; @@ -351,6 +352,7 @@ export function UnifiedChat() { "image" | "document" | "voice" | "usage" | "tokens" >("image"); const [documentPlatformDialogOpen, setDocumentPlatformDialogOpen] = useState(false); + const [contextLimitDialogOpen, setContextLimitDialogOpen] = useState(false); // Audio recording states const [isRecording, setIsRecording] = useState(false); @@ -1467,7 +1469,6 @@ export function UnifiedChat() { // Store the original input and attachments in case we need to restore them const originalInput = trimmedInput; const originalImages = [...draftImages]; - const originalImageUrls = new Map(imageUrls); const originalDocumentText = documentText; const originalDocumentName = documentName; @@ -1525,16 +1526,54 @@ export function UnifiedChat() { console.error("Failed to send message:", error); // Handle usage limit errors with upsell dialogs - // The SDK throws errors with the message "Request failed with status 403: {json}" + // The SDK throws errors with the message "Request failed with status 403: {json}" or "Request failed with status 413: {json}" // We need to parse this to extract the actual error details let errorMessage = error instanceof Error ? error.message : "Something went wrong"; // Also check the cause property if it exists const causeMessage = (error as Error & { cause?: { message?: string } })?.cause?.message; - if (causeMessage && causeMessage.includes("Request failed with status 403:")) { + if (causeMessage && causeMessage.includes("Request failed with status")) { errorMessage = causeMessage; } + // Check for 413 error (Message exceeds context limit) + let status413Error: { status: number; message: string } | null = null; + if (errorMessage.includes("Request failed with status 413:")) { + try { + // Extract the JSON part from the error message + const jsonMatch = errorMessage.match(/Request failed with status 413:\s*({.*})/); + if (jsonMatch && jsonMatch[1]) { + status413Error = JSON.parse(jsonMatch[1]); + } + } catch (parseError) { + console.error("Failed to parse 413 error:", parseError); + } + } + + if (status413Error && status413Error.message === "Message exceeds context limit") { + // Remove the user message from history and restore input + setMessages((prev) => prev.filter((msg) => msg.id !== localMessageId)); + + // Restore the original input and attachments + if (!overrideInput) { + setInput(originalInput); + setDraftImages(originalImages); + // Re-create object URLs since originals were revoked by clearAllAttachments() + const restoredUrlMap = new Map(); + for (const file of originalImages) { + restoredUrlMap.set(file, URL.createObjectURL(file)); + } + setImageUrls(restoredUrlMap); + setDocumentText(originalDocumentText); + setDocumentName(originalDocumentName); + } + + // Show the context limit dialog + setContextLimitDialogOpen(true); + setError("Your message exceeds the context limit for this model."); + return; // Exit early, don't continue to other error handling + } + let status403Error: { status: number; message: string } | null = null; // Check if this is a 403 error from the SDK @@ -1558,7 +1597,12 @@ export function UnifiedChat() { if (!overrideInput) { setInput(originalInput); setDraftImages(originalImages); - setImageUrls(originalImageUrls); + // Re-create object URLs since originals were revoked by clearAllAttachments() + const restoredUrlMap = new Map(); + for (const file of originalImages) { + restoredUrlMap.set(file, URL.createObjectURL(file)); + } + setImageUrls(restoredUrlMap); setDocumentText(originalDocumentText); setDocumentName(originalDocumentName); } @@ -1657,7 +1701,12 @@ export function UnifiedChat() { if (!overrideInput) { setInput(originalInput); setDraftImages(originalImages); - setImageUrls(originalImageUrls); + // Re-create object URLs since originals were revoked by clearAllAttachments() + const restoredUrlMap = new Map(); + for (const file of originalImages) { + restoredUrlMap.set(file, URL.createObjectURL(file)); + } + setImageUrls(restoredUrlMap); setDocumentText(originalDocumentText); setDocumentName(originalDocumentName); } @@ -1678,7 +1727,12 @@ export function UnifiedChat() { if (!overrideInput) { setInput(originalInput); setDraftImages(originalImages); - setImageUrls(originalImageUrls); + // Re-create object URLs since originals were revoked by clearAllAttachments() + const restoredUrlMap = new Map(); + for (const file of originalImages) { + restoredUrlMap.set(file, URL.createObjectURL(file)); + } + setImageUrls(restoredUrlMap); setDocumentText(originalDocumentText); setDocumentName(originalDocumentName); } @@ -2235,6 +2289,14 @@ export function UnifiedChat() { hasProAccess={canUseDocuments || false} /> + {/* Context limit dialog for 413 errors */} + + {/* Hidden file inputs - must be outside conditional rendering to work in both views */}