Skip to main content
Glama
LocalizationAnalyzer.tsx6.94 kB
'use client'; import { extractErrorMessage } from '@intlayer/config/client'; import { usePersistedStore } from '@intlayer/design-system/hooks'; import { useIntlayer } from 'next-intlayer'; import { type FC, useEffect, useRef, useState } from 'react'; import { useSearchParamState } from '@/hooks/useSearchParamState'; import { AnalyzerLoading } from './Analyzer/AnalyzerLoading'; import { AnalyzerForm } from './Analyzer/Form/AnalyzerForm'; import { useAnalyzerUrlSchema } from './Analyzer/Form/useAnalyzerUrlSchema'; import { AnalyzerPageResults } from './Analyzer/Results/AnalyzerPageResults'; import { AnalyzerSiteResults } from './Analyzer/Results/AnalyzerSiteResults'; import { RobotsSection } from './Analyzer/Results/RobotsSection'; import { SitemapSection } from './Analyzer/Results/SitemapSection'; import type { AuditEvent, DomainData, MergedData, } from './Analyzer/Results/types'; export const LocalizationAnalyzer: FC = () => { const { globalError: globalErrorMessage } = useIntlayer( 'localization-analyzer' ); const [error, setError] = useState<string | null>(null); const [isLoading, setIsLoading] = useState<boolean>(false); const [progress, setProgress] = usePersistedStore<number>( 'localization-analyzer-progress', 0 ); const [stepsMessage, setStepsMessage] = usePersistedStore<string>( 'localization-analyzer-steps-message', '' ); const [score, setScore] = usePersistedStore<number>( 'localization-analyzer-score', 0 ); const [domainData, setDomainData] = usePersistedStore< Partial<DomainData> | undefined >('localization-analyzer-domain-data', undefined); const [mergedData, setMergedData] = usePersistedStore<MergedData>( 'localization-analyzer-data', {} ); const urlSchema = useAnalyzerUrlSchema(); const eventSourceRef = useRef<EventSource | null>(null); const analyzedUrlRef = useRef<string | null>(null); const { params } = useSearchParamState({ auto_start: { type: 'boolean', fallbackValue: false }, url: { type: 'string', fallbackValue: '' }, }); // Cleanup EventSource on unmount useEffect(() => { return () => { if (eventSourceRef.current) { eventSourceRef.current.close(); eventSourceRef.current = null; } }; }, []); useEffect(() => { // Only auto-start if: // 1. auto_start is true // 2. URL is provided // 3. We haven't already analyzed this URL // 4. Not currently loading // 5. No existing data (first time or after reset) if ( params.auto_start && params.url && analyzedUrlRef.current !== params.url && !isLoading && Object.keys(mergedData).length === 0 ) { try { // Validate the URL (adjust schema call as needed) urlSchema.parse({ url: params.url }); } catch (error) { setError(`Invalid URL: ${error}`); return; } analyzedUrlRef.current = params.url; handleAnalyze(params.url); } }, [params.auto_start, params.url, isLoading]); const handleAnalyze = async (url: string) => { // Close any existing EventSource connection if (eventSourceRef.current) { eventSourceRef.current.close(); eventSourceRef.current = null; } setError(null); setIsLoading(true); setProgress(0); setStepsMessage('Starting analysis...'); setMergedData({}); setDomainData(undefined); try { const eventSource = new EventSource( `${process.env.NEXT_PUBLIC_SCANNER_API_URL}?url=${encodeURIComponent(url)}` ); eventSourceRef.current = eventSource; eventSource.onmessage = (event) => { try { const message: AuditEvent = JSON.parse(event.data); if (typeof message.globalError === 'string') { console.error(message.globalError); setError(globalErrorMessage); eventSource.close(); eventSourceRef.current = null; setIsLoading(false); return; } if (typeof message.message === 'string') { setStepsMessage(message.message); } if (typeof message.progress === 'number') { setProgress(message.progress ?? 0); } if (typeof message.score === 'number') { setScore(message.score); } if (typeof message.type === 'string') { console.log(message); setMergedData((prev) => ({ ...prev, [message.type!]: { status: message.status, data: message.data, }, })); } if (typeof message.domainData === 'object') { setDomainData((prev) => ({ ...prev, ...message.domainData })); } // Check if analysis is complete (only when progress reaches 100) if ( typeof message.progress === 'number' && message.progress === 100 ) { setIsLoading(false); // Keep connection open briefly to ensure all messages are received setTimeout(() => { if (eventSourceRef.current) { eventSourceRef.current.close(); eventSourceRef.current = null; } }, 1000); } } catch (err) { console.error('Failed to parse SSE message:', err); } }; // eventSource.onerror = (error) => { // console.error('SSE error:', error); // setError('Connection error while analyzing site.'); // eventSource.close(); // eventSourceRef.current = null; // setIsLoading(false); // }; } catch (error) { setMergedData({}); setError(extractErrorMessage(error)); setIsLoading(false); } }; const hasData = mergedData && Object.keys(mergedData).length > 0; return ( <div className="flex w-full flex-col items-center justify-center py-6 text-center"> <AnalyzerForm onAnalyze={handleAnalyze} loading={isLoading} /> {isLoading && ( <AnalyzerLoading progress={progress} currentStep={stepsMessage ?? 'Analyzing...'} /> )} {(hasData || isLoading) && ( <div className="mt-10 w-full max-w-2xl rounded-2xl bg-card p-6 shadow-md"> <AnalyzerSiteResults domainData={domainData} score={score} isLoading={isLoading} /> <AnalyzerPageResults data={mergedData} url={params.url} isLoading={isLoading} /> <RobotsSection data={mergedData} isLoading={isLoading} /> <SitemapSection data={mergedData} isLoading={isLoading} /> {/* <PageGroupsList domainData={domainData} onSelect={() => null} /> */} </div> )} {error && <p className="text-error">{error}</p>} </div> ); };

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/aymericzip/intlayer'

If you have feedback or need assistance with the MCP directory API, please join our Discord server