diff --git a/src/components/ChatInterface.tsx b/src/components/ChatInterface.tsx index 4a33a2e2..9f2d578d 100644 --- a/src/components/ChatInterface.tsx +++ b/src/components/ChatInterface.tsx @@ -37,6 +37,7 @@ import { streamAIResponse, generateChatTitleFromMessages, generateAIResponse } f import { executeCode, startSandbox } from '@/lib/sandbox.ts'; import { useAuth } from '@/hooks/useAuth'; import { useAuth as useClerkAuth } from '@clerk/clerk-react'; +import { useUsageTracking } from '@/hooks/useUsageTracking'; import { E2BCodeExecution } from './E2BCodeExecution'; import AnimatedResultShowcase, { type ShowcaseExecutionResult } from './AnimatedResultShowcase'; import { braveSearchService, type BraveSearchResult, type WebsiteAnalysis } from '@/lib/search-service'; @@ -129,6 +130,7 @@ interface CodeBlock { const ChatInterface: React.FC = () => { const { user, isLoading: authLoading } = useAuth(); const { getToken } = useClerkAuth(); + const { getSubscription } = useUsageTracking(); const [input, setInput] = useState(''); const [isTyping, setIsTyping] = useState(false); const [selectedChatId, setSelectedChatId] = useState(null); @@ -384,7 +386,12 @@ const ChatInterface: React.FC = () => { // Create user message await createMessage(messageData); - await refetchCustomer(); + // Refresh user subscription data after usage + try { + await getSubscription(); + } catch (error) { + console.debug('Failed to refresh subscription data:', error); + } // Auto-run user code blocks via E2B (JS/TS only per project preference) const userBlocks = extractCodeBlocks(userContent); @@ -487,7 +494,12 @@ const ChatInterface: React.FC = () => { // Create assistant message await createMessage(assistantMessageData); - await refetchCustomer(); + // Refresh user subscription data after usage + try { + await getSubscription(); + } catch (error) { + console.debug('Failed to refresh subscription data:', error); + } // Try AI title refinement after first exchange try { diff --git a/src/components/EnhancedChatInterface.tsx b/src/components/EnhancedChatInterface.tsx new file mode 100644 index 00000000..7c5b7290 --- /dev/null +++ b/src/components/EnhancedChatInterface.tsx @@ -0,0 +1,1126 @@ +import React, { useState, useRef, useEffect } from 'react'; +import { Button } from '@/components/ui/button'; +import { Textarea } from '@/components/ui/textarea'; +import { Card, CardContent } from '@/components/ui/card'; +import { ScrollArea } from '@/components/ui/scroll-area'; +import { Separator } from '@/components/ui/separator'; +import { Badge } from '@/components/ui/badge'; +import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'; +import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from '@/components/ui/dialog'; +import { Input } from '@/components/ui/input'; +import { SafeText } from '@/components/ui/SafeText'; +import { + Send, + User, + Bot, + Play, + Copy, + Check, + Plus, + MessageSquare, + Trash2, + Edit3, + Sparkles, + Clock, + Zap, + Loader2, + Search, + Globe, + ExternalLink, + Link, + Code, + Palette, + Layers, + ArrowUp, + Mic, + Paperclip, + Settings +} from 'lucide-react'; +import { motion, AnimatePresence } from 'framer-motion'; +import { useQuery, useMutation } from 'convex/react'; +import { api } from '../../convex/_generated/api'; +import type { Id } from '../../convex/_generated/dataModel'; +import { streamAIResponse, generateChatTitleFromMessages, generateAIResponse } from '@/lib/ai'; +import { executeCode, startSandbox } from '@/lib/sandbox'; +import { useAuth } from '@/hooks/useAuth'; +import { useAuth as useClerkAuth } from '@clerk/clerk-react'; +import { useUsageTracking } from '@/hooks/useUsageTracking'; +import AnimatedResultShowcase from './AnimatedResultShowcase'; +import { braveSearchService, type BraveSearchResult, type WebsiteAnalysis } from '@/lib/search-service'; +import { crawlSite } from '@/lib/firecrawl'; +import { toast } from 'sonner'; +import * as Sentry from '@sentry/react'; +import WebContainerFailsafe from './WebContainerFailsafe'; +import { DECISION_PROMPT_NEXT } from '@/lib/decisionPrompt'; + +const { logger } = Sentry; + +// Security constants for input validation +const MAX_MESSAGE_LENGTH = 10000; +const MAX_TITLE_LENGTH = 100; +const MIN_TITLE_LENGTH = 1; + +// XSS protection: sanitize text input +const sanitizeText = (text: string): string => { + return text + .replace(/[<>'"&]/g, (char) => { + const chars: { [key: string]: string } = { + '<': '<', + '>': '>', + "'": ''', + '"': '"', + '&': '&' + }; + return chars[char] || char; + }) + .trim(); +}; + +// Validate input length and content +const validateInput = (text: string, maxLength: number): { isValid: boolean; error?: string } => { + if (!text || text.trim().length === 0) { + return { isValid: false, error: 'Input cannot be empty' }; + } + if (text.length > maxLength) { + return { isValid: false, error: `Input too long. Maximum ${maxLength} characters allowed` }; + } + // Check for potentially malicious patterns + const suspiciousPatterns = [ + /