import { useState, useEffect } from 'react' import { cn } from '@/lib/utils' import { useWritingCheck } from '@/hooks/use-writing-check' import { getRemainingChecks } from '@/utils/rate-limiter' import { useRequireAuth } from '@/hooks/use-require-auth' import { useAuthStore } from '@/store/auth-store' import { countTodayWritingSubmissions } from '@/lib/progress-service' import { useAwardActivity } from '@/hooks/use-gamification' import { XP_REWARDS } from '@/lib/gamification-service' const MAX_CHARS = 1000 const GUEST_LIMIT = 3 const AUTH_LIMIT = 10 // Extract a string field from partial JSON stream function extractTextField(partial: string, field: string): string { const m = partial.match(new RegExp(`"${field}"\\s*:\\s*"((?:[^"\\\\]|\\\\.)*)`)) if (!m) return '' return m[1].replace(/\\n/g, '\n').replace(/\\"/g, '"') } // Extract score from partial JSON stream as soon as the field is complete function extractScore(partial: string): string | null { const m = partial.match(/"score"\s*:\s*"([^"]+)"/) return m ? m[1] : null } // Extract completed array items from a partial JSON array field function extractArrayField(partial: string, field: string): string[] { const m = partial.match(new RegExp(`"${field}"\\s*:\\s*\\[([^\\]]*)`)) if (!m) return [] const items: string[] = [] const re = /"((?:[^"\\]|\\.)*)"/g let match while ((match = re.exec(m[1])) !== null) { items.push(match[1].replace(/\\n/g, '\n').replace(/\\"/g, '"')) } return items } export function WritingChecker() { const [text, setText] = useState('') const [improvedExpanded, setImprovedExpanded] = useState(false) const [remaining, setRemaining] = useState(getRemainingChecks) const [streamingText, setStreamingText] = useState('') const { mutate: checkWriting, isPending, isError, error, data: feedback, reset: resetMutation } = useWritingCheck() const { requireAuth } = useRequireAuth() const user = useAuthStore((s) => s.user) const { mutate: awardActivity } = useAwardActivity() const dailyLimit = user ? AUTH_LIMIT : GUEST_LIMIT // Fetch server-side remaining count for authenticated users useEffect(() => { if (!user) { setRemaining(getRemainingChecks()) resetMutation() return } countTodayWritingSubmissions(user.id).then((used) => { setRemaining(AUTH_LIMIT - used) }) }, [user, resetMutation]) const streamingScore = isPending ? extractScore(streamingText) : null const streamingGrammar = isPending ? extractArrayField(streamingText, 'grammar') : [] const streamingVocab = isPending ? extractArrayField(streamingText, 'vocabulary') : [] const streamingStructure = isPending ? extractTextField(streamingText, 'structure') : '' const streamingSummary = isPending ? extractTextField(streamingText, 'summary') : '' const charCount = text.length const canSubmit = text.trim().length > 0 && remaining > 0 && charCount <= MAX_CHARS && !isPending function handleSubmit() { if (!requireAuth()) return if (!canSubmit) return setStreamingText('') checkWriting( { content: text, onChunk: (chunk) => setStreamingText((prev) => prev + chunk) }, { onSuccess: () => { setStreamingText('') if (user) { awardActivity({ xp: XP_REWARDS.writing }) countTodayWritingSubmissions(user.id).then((used) => setRemaining(AUTH_LIMIT - used)) } else { setRemaining(getRemainingChecks()) } }, onError: () => { setStreamingText('') if (!user) setRemaining(getRemainingChecks()) }, }, ) } const sentenceCount = text.split(/[.!?]+/).filter(s => s.trim()).length const wordCount = text.split(/\s+/).filter(Boolean).length return (
Dán bài viết — AI sẽ kiểm tra ngữ pháp, chính tả, và chấm điểm IELTS/TOEIC
Bạn đã dùng hết {dailyLimit} lượt hôm nay. Vui lòng quay lại vào ngày mai.
{(error as Error)?.message ?? 'Đã có lỗi xảy ra. Vui lòng thử lại.'}
{streamingStructure}
) : ({streamingSummary}
) : ({feedback.structure}
{feedback.improvedVersion}
)}{feedback.summary}