diff --git a/src/actions/check-web-content-analysis-credits.ts b/src/actions/check-web-content-analysis-credits.ts deleted file mode 100644 index 3ca2c7f..0000000 --- a/src/actions/check-web-content-analysis-credits.ts +++ /dev/null @@ -1,37 +0,0 @@ -'use server'; - -import { getWebContentAnalysisCost } from '@/ai/text/utils/web-content-analyzer-config'; -import { getUserCredits, hasEnoughCredits } from '@/credits/credits'; -import type { User } from '@/lib/auth-types'; -import { userActionClient } from '@/lib/safe-action'; - -/** - * Check if user has enough credits for web content analysis - */ -export const checkWebContentAnalysisCreditsAction = userActionClient.action( - async ({ ctx }) => { - const currentUser = (ctx as { user: User }).user; - - try { - const requiredCredits = getWebContentAnalysisCost(); - const currentCredits = await getUserCredits(currentUser.id); - const hasCredits = await hasEnoughCredits({ - userId: currentUser.id, - requiredCredits, - }); - - return { - success: true, - hasEnoughCredits: hasCredits, - currentCredits, - requiredCredits, - }; - } catch (error) { - console.error('check web content analysis credits error:', error); - return { - success: false, - error: error instanceof Error ? error.message : 'Something went wrong', - }; - } - } -); diff --git a/src/ai/text/components/error-display.tsx b/src/ai/text/components/error-display.tsx index 489ddd6..43a9435 100644 --- a/src/ai/text/components/error-display.tsx +++ b/src/ai/text/components/error-display.tsx @@ -34,7 +34,6 @@ interface ErrorDisplayProps { const errorIcons = { [ErrorType.VALIDATION]: AlertCircleIcon, [ErrorType.NETWORK]: WifiOffIcon, - [ErrorType.CREDITS]: CreditCardIcon, [ErrorType.SCRAPING]: ServerIcon, [ErrorType.ANALYSIS]: HelpCircleIcon, [ErrorType.TIMEOUT]: ClockIcon, @@ -84,7 +83,6 @@ const severityColors = { const errorTitles = { [ErrorType.VALIDATION]: 'Invalid Input', [ErrorType.NETWORK]: 'Connection Error', - [ErrorType.CREDITS]: 'Insufficient Credits', [ErrorType.SCRAPING]: 'Unable to Access Website', [ErrorType.ANALYSIS]: 'Analysis Failed', [ErrorType.TIMEOUT]: 'Request Timed Out', diff --git a/src/ai/text/components/url-input-form.tsx b/src/ai/text/components/url-input-form.tsx index 504d7de..8528b2e 100644 --- a/src/ai/text/components/url-input-form.tsx +++ b/src/ai/text/components/url-input-form.tsx @@ -1,9 +1,7 @@ 'use client'; -import { checkWebContentAnalysisCreditsAction } from '@/actions/check-web-content-analysis-credits'; import type { UrlInputFormProps } from '@/ai/text/utils/web-content-analyzer'; import { webContentAnalyzerConfig } from '@/ai/text/utils/web-content-analyzer-config'; -import { LoginWrapper } from '@/components/auth/login-wrapper'; import { Button } from '@/components/ui/button'; import { Form, @@ -20,21 +18,10 @@ import { SelectTrigger, SelectValue, } from '@/components/ui/select'; -import { useLocalePathname } from '@/i18n/navigation'; -import { authClient } from '@/lib/auth-client'; import { zodResolver } from '@hookform/resolvers/zod'; -import { - AlertCircleIcon, - CoinsIcon, - LinkIcon, - Loader2Icon, - LogInIcon, - SparklesIcon, -} from 'lucide-react'; -import { useAction } from 'next-safe-action/hooks'; +import { LinkIcon, Loader2Icon, SparklesIcon } from 'lucide-react'; import { useEffect, useState } from 'react'; import { useForm } from 'react-hook-form'; -import { toast } from 'sonner'; import { z } from 'zod'; import { useDebounce } from '../utils/performance'; @@ -52,19 +39,9 @@ export function UrlInputForm({ modelProvider, setModelProvider, }: UrlInputFormProps) { - const [creditInfo, setCreditInfo] = useState<{ - hasEnoughCredits: boolean; - currentCredits: number; - requiredCredits: number; - } | null>(null); const [mounted, setMounted] = useState(false); - // Get authentication status and current path for callback - const { data: session, isPending: isAuthLoading } = authClient.useSession(); - const isAuthenticated = !!session?.user; - const currentPath = useLocalePathname(); - - // Prevent hydration mismatch by only rendering auth-dependent content after mount + // Prevent hydration mismatch by only rendering content after mount useEffect(() => { setMounted(true); }, []); @@ -84,42 +61,6 @@ export function UrlInputForm({ webContentAnalyzerConfig.performance.urlInputDebounceMs ); - const { execute: checkCredits, isExecuting: isCheckingCredits } = useAction( - checkWebContentAnalysisCreditsAction, - { - onSuccess: (result) => { - if (result.data?.success) { - setCreditInfo({ - hasEnoughCredits: result.data.hasEnoughCredits ?? false, - currentCredits: result.data.currentCredits ?? 0, - requiredCredits: result.data.requiredCredits ?? 0, - }); - } else { - // Only show error toast if it's not an auth error - if (result.data?.error !== 'Unauthorized') { - setTimeout(() => { - toast.error(result.data?.error || 'Failed to check credits'); - }, 0); - } - } - }, - onError: (error) => { - console.error('Credit check error:', error); - // Only show error toast for non-auth errors - setTimeout(() => { - toast.error('Failed to check credits'); - }, 0); - }, - } - ); - - // Check credits only when user is authenticated - useEffect(() => { - if (isAuthenticated && !isAuthLoading) { - checkCredits(); - } - }, [isAuthenticated, isAuthLoading, checkCredits]); - // Debounced URL validation effect useEffect(() => { if (debouncedUrl && debouncedUrl !== urlValue) { @@ -129,23 +70,12 @@ export function UrlInputForm({ }, [debouncedUrl, urlValue, form]); const handleSubmit = (data: UrlFormData) => { - // For authenticated users, check credits before submitting - if (creditInfo && !creditInfo.hasEnoughCredits) { - // Defer toast to avoid flushSync during render - setTimeout(() => { - toast.error( - `Insufficient credits. You need ${creditInfo.requiredCredits} credits but only have ${creditInfo.currentCredits}.` - ); - }, 0); - return; - } onSubmit(data.url ?? '', modelProvider); }; const handleFormSubmit = form.handleSubmit(handleSubmit); - const isInsufficientCredits = creditInfo && !creditInfo.hasEnoughCredits; - const isFormDisabled = isLoading || disabled || !!isInsufficientCredits; + const isFormDisabled = isLoading || disabled; return ( <> @@ -194,67 +124,20 @@ export function UrlInputForm({ )} /> - {/* Credit Information - Only show for authenticated users */} - {isAuthenticated && creditInfo && ( -
-
- - - Cost: {creditInfo.requiredCredits} credits - -
-
- - Balance: {creditInfo.currentCredits} - - {!creditInfo.hasEnoughCredits && ( - - )} -
-
- )} - - {/* Insufficient Credits Warning */} - {isAuthenticated && isInsufficientCredits && ( -
- - - Insufficient credits. You need {creditInfo.requiredCredits}{' '} - credits but only have {creditInfo.currentCredits}. - -
- )} - {!mounted ? ( // Show loading state during hydration to prevent mismatch - ) : isAuthenticated ? ( + ) : ( - ) : ( - - - )} diff --git a/src/ai/text/components/web-content-analyzer.tsx b/src/ai/text/components/web-content-analyzer.tsx index e2b23d2..64efc6f 100644 --- a/src/ai/text/components/web-content-analyzer.tsx +++ b/src/ai/text/components/web-content-analyzer.tsx @@ -232,16 +232,6 @@ export function WebContentAnalyzer({ className }: WebContentAnalyzerProps) { errorType = ErrorType.VALIDATION; retryable = false; break; - case 401: - errorType = ErrorType.AUTHENTICATION; - severity = ErrorSeverity.HIGH; - retryable = false; - break; - case 402: - errorType = ErrorType.CREDITS; - severity = ErrorSeverity.HIGH; - retryable = false; - break; case 408: errorType = ErrorType.TIMEOUT; break; diff --git a/src/ai/text/utils/error-handling.ts b/src/ai/text/utils/error-handling.ts index 8896d5e..ea648a6 100644 --- a/src/ai/text/utils/error-handling.ts +++ b/src/ai/text/utils/error-handling.ts @@ -9,7 +9,6 @@ import { webContentAnalyzerConfig } from '@/ai/text/utils/web-content-analyzer-c export enum ErrorType { VALIDATION = 'validation', NETWORK = 'network', - CREDITS = 'credits', SCRAPING = 'scraping', ANALYSIS = 'analysis', TIMEOUT = 'timeout', @@ -96,22 +95,6 @@ export function classifyError(error: unknown): WebContentAnalyzerError { ); } - // Credit errors - if ( - message.includes('credit') || - message.includes('insufficient') || - message.includes('balance') - ) { - return new WebContentAnalyzerError( - ErrorType.CREDITS, - error.message, - 'Insufficient credits to perform analysis. Please purchase more credits.', - ErrorSeverity.HIGH, - false, - error - ); - } - // Scraping errors if ( message.includes('scrape') || @@ -278,16 +261,6 @@ export function getRecoveryActions(error: WebContentAnalyzerError): Array<{ { label: 'Try Simpler URL', action: 'simplify_url' }, ]; - case ErrorType.CREDITS: - return [ - { - label: 'Purchase Credits', - action: 'purchase_credits', - primary: true, - }, - { label: 'Check Balance', action: 'check_balance' }, - ]; - case ErrorType.SCRAPING: return [ { label: 'Try Again', action: 'retry', primary: true }, diff --git a/src/ai/text/utils/web-content-analyzer-config.ts b/src/ai/text/utils/web-content-analyzer-config.ts index 83b183c..078cb14 100644 --- a/src/ai/text/utils/web-content-analyzer-config.ts +++ b/src/ai/text/utils/web-content-analyzer-config.ts @@ -6,11 +6,6 @@ */ export const webContentAnalyzerConfig = { - /** - * Credit cost for performing a web content analysis - */ - creditsCost: 100, - /** * Maximum content length for AI analysis (in characters) * Optimized to prevent token limit issues while maintaining quality @@ -118,21 +113,14 @@ export const webContentAnalyzerConfig = { maxTokens: 2000, }, openrouter: { - model: 'openrouter/horizon-beta', + // model: 'openrouter/horizon-beta', // model: 'x-ai/grok-3-beta', - // model: 'openai/gpt-4o-mini', + model: 'openai/gpt-4o-mini', temperature: 0.1, maxTokens: 2000, }, } as const; -/** - * Get the credit cost for web content analysis - */ -export function getWebContentAnalysisCost(): number { - return webContentAnalyzerConfig.creditsCost; -} - /** * Validates if the Firecrawl API key is configured */ @@ -151,8 +139,6 @@ export function validateFirecrawlConfig(): boolean { */ export function validateWebContentAnalyzerConfig(): boolean { return ( - typeof webContentAnalyzerConfig.creditsCost === 'number' && - webContentAnalyzerConfig.creditsCost > 0 && typeof webContentAnalyzerConfig.maxContentLength === 'number' && webContentAnalyzerConfig.maxContentLength > 0 && typeof webContentAnalyzerConfig.timeoutMillis === 'number' && diff --git a/src/app/api/analyze-content/route.ts b/src/app/api/analyze-content/route.ts index 91db24e..ef01183 100644 --- a/src/app/api/analyze-content/route.ts +++ b/src/app/api/analyze-content/route.ts @@ -13,12 +13,9 @@ import { validateUrl, } from '@/ai/text/utils/web-content-analyzer'; import { - getWebContentAnalysisCost, validateFirecrawlConfig, webContentAnalyzerConfig, } from '@/ai/text/utils/web-content-analyzer-config'; -import { consumeCredits, hasEnoughCredits } from '@/credits/credits'; -import { getSession } from '@/lib/server'; import { createDeepSeek } from '@ai-sdk/deepseek'; import { createGoogleGenerativeAI } from '@ai-sdk/google'; import { createOpenAI } from '@ai-sdk/openai'; @@ -30,7 +27,6 @@ import { z } from 'zod'; // Constants from configuration const TIMEOUT_MILLIS = webContentAnalyzerConfig.timeoutMillis; -const CREDITS_COST = getWebContentAnalysisCost(); const MAX_CONTENT_LENGTH = webContentAnalyzerConfig.maxContentLength; // Initialize Firecrawl client @@ -361,28 +357,6 @@ export async function POST(req: NextRequest) { ); } - // Check authentication - const session = await getSession(); - if (!session) { - const authError = new WebContentAnalyzerError( - ErrorType.AUTHENTICATION, - 'Authentication required', - 'Please sign in to analyze web content.', - ErrorSeverity.HIGH, - false - ); - - logError(authError, { requestId }); - - return NextResponse.json( - { - success: false, - error: authError.userMessage, - } satisfies AnalyzeContentResponse, - { status: 401 } - ); - } - // Check if Firecrawl is configured if (!validateFirecrawlConfig()) { const configError = new WebContentAnalyzerError( @@ -404,39 +378,7 @@ export async function POST(req: NextRequest) { ); } - // Check if user has sufficient credits before starting analysis - const hasCredits = await hasEnoughCredits({ - userId: session.user.id, - requiredCredits: CREDITS_COST, - }); - - if (!hasCredits) { - const creditError = new WebContentAnalyzerError( - ErrorType.CREDITS, - 'Insufficient credits to perform analysis', - "You don't have enough credits to analyze this webpage. Please purchase more credits.", - ErrorSeverity.HIGH, - false - ); - - logError(creditError, { - requestId, - userId: session.user.id, - requiredCredits: CREDITS_COST, - }); - - return NextResponse.json( - { - success: false, - error: creditError.userMessage, - } satisfies AnalyzeContentResponse, - { status: 402 } - ); - } - - console.log( - `Starting analysis [requestId=${requestId}, url=${url}, userId=${session.user.id}]` - ); + console.log(`Starting analysis [requestId=${requestId}, url=${url}]`); // Perform analysis with timeout and enhanced error handling const analysisPromise = (async () => { @@ -447,13 +389,6 @@ export async function POST(req: NextRequest) { // Step 2: Analyze content with AI (pass provider) const analysis = await analyzeContent(content, url, modelProvider); - // Step 3: Consume credits (only on successful analysis) - await consumeCredits({ - userId: session.user.id, - amount: CREDITS_COST, - description: `Web content analysis: ${url}`, - }); - return { analysis, screenshot }; } catch (error) { // If it's already a WebContentAnalyzerError, just re-throw @@ -477,7 +412,6 @@ export async function POST(req: NextRequest) { return NextResponse.json({ success: true, data: result, - creditsConsumed: CREDITS_COST, } satisfies AnalyzeContentResponse); } catch (error) { const elapsed = ((performance.now() - startTime) / 1000).toFixed(1); @@ -499,12 +433,6 @@ export async function POST(req: NextRequest) { case ErrorType.VALIDATION: statusCode = 400; break; - case ErrorType.AUTHENTICATION: - statusCode = 401; - break; - case ErrorType.CREDITS: - statusCode = 402; - break; case ErrorType.TIMEOUT: statusCode = 408; break;