acecalisto3's picture
Rename index.html to app.tsx
3dadbd0 verified
raw
history blame
17 kB
import React, { useState, useEffect, useRef } from 'react';
import { LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, Legend, ResponsiveContainer } from "recharts";
type ChatMessage = {
role: 'user' | 'system';
content: string;
};
const App: React.FC = () => {
const [urlInput, setUrlInput] = useState<string>('https://www.example.com');
const [bulkToggle, setBulkToggle] = useState<boolean>(false);
const [actionRadio, setActionRadio] = useState<'Scrape data' | 'Capture image' | 'Both'>('Both');
const [maxUrls, setMaxUrls] = useState<number>(5);
const [crawlDepth, setCrawlDepth] = useState<number>(1);
const [scrapedDataOutput, setScrapedDataOutput] = useState<string>('');
const [screenshotOutput, setScreenshotOutput] = useState<string | null>(null);
const [monitorUrlInput, setMonitorUrlInput] = useState<string>('');
const [intervalInput, setIntervalInput] = useState<number>(300);
const [changeOutput, setChangeOutput] = useState<string>('');
const [chatHistory, setChatHistory] = useState<ChatMessage[]>([]);
const [isMonitoring, setIsMonitoring] = useState<boolean>(false);
const [monitoringData, setMonitoringData] = useState<{ time: string; changes: number }[]>([]);
const [isProcessing, setIsProcessing] = useState<boolean>(false);
const [error, setError] = useState<string | null>(null);
const wsRef = useRef<WebSocket | null>(null);
useEffect(() => {
let ws: WebSocket | null = null;
if (isMonitoring) {
ws = new WebSocket('ws://localhost:8000/ws');
wsRef.current = ws;
ws.onopen = () => {
console.log("Connected to WebSocket server.");
};
ws.onmessage = (event) => {
const message = JSON.parse(event.data);
if (message.type === "change_detected") {
setChangeOutput(prev => prev + `Change detected at ${message.url}\n`);
setChatHistory(prev => [...prev, { role: 'system', content: `Change detected at ${message.url}` }]);
setMonitoringData(prev => {
const now = new Date();
const time = now.toLocaleTimeString();
return [...prev, { time, changes: 1 }];
});
} else if (message.type === "server_log") {
console.log("Server Log:", message.log);
} else if (message.type === "server_error") {
setError(message.error);
}
};
ws.onclose = () => {
console.log("Disconnected from WebSocket server.");
};
ws.onerror = (error) => {
console.error("WebSocket error:", error);
setError("WebSocket connection error.");
};
} else if (wsRef.current) {
wsRef.current.close();
wsRef.current = null;
}
return () => {
if (wsRef.current) {
wsRef.current.close();
}
};
}, [isMonitoring]);
const handleProcessUrls = async () => {
setIsProcessing(true);
setError(null);
try {
const response = await fetch('http://localhost:8000/process_urls', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
url_input: urlInput,
bulk_toggle: bulkToggle,
action_radio: actionRadio,
max_urls: maxUrls,
crawl_depth: crawlDepth,
}),
});
if (!response.ok) {
const errorData = await response.json();
throw new Error(`HTTP error! Status: ${response.status}, Message: ${errorData.message || 'Unknown error'}`);
}
const data = await response.json();
setScrapedDataOutput(JSON.stringify(data.scraped_data, null, 2));
if (data.screenshot_data) {
setScreenshotOutput(data.screenshot_data);
} else {
setScreenshotOutput(null);
}
setError(null);
} catch (e: any) {
console.error("Error processing URLs:", e);
setError(e.message);
setScrapedDataOutput('');
setScreenshotOutput(null);
} finally {
setIsProcessing(false);
}
};
const handleChatSubmit = async (user_input: string) => {
setChatHistory(prev => [...prev, { role: 'user', content: user_input }]);
if (user_input.toLowerCase().includes("start monitoring")) {
setIsMonitoring(true);
setChatHistory(prev => [...prev, { role: 'system', content: "Monitoring started." }]);
} else if (user_input.toLowerCase().includes("stop monitoring")) {
setIsMonitoring(false);
setChatHistory(prev => [...prev, { role: 'system', content: "Monitoring stopped." }]);
} else if (user_input.toLowerCase().includes("help")) {
setChatHistory(prev => [...prev, { role: 'system', content: "Commands: 'Start monitoring', 'Stop monitoring', 'Help'" }]);
} else {
setChatHistory(prev => [...prev, { role: 'system', content: "Unknown command. Type 'Help' for available commands." }]);
}
};
const handleStartMonitoring = () => {
setIsMonitoring(true);
setChatHistory(prev => [...prev, { role: 'system', content: "Monitoring started." }]);
};
const handleStopMonitoring = () => {
setIsMonitoring(false);
setChatHistory(prev => [...prev, { role: 'system', content: "Monitoring stopped." }]);
setMonitoringData([]);
};
return (
<div className="bg-gray-100 min-h-screen p-4">
<h1 className="text-3xl font-bold text-center text-gray-800 mb-8">Smart Scraper with Change Detection</h1>
{error && <div className="bg-red-200 text-red-700 rounded-md p-2 mb-4">{error}</div>}
<div className="flex flex-col md:flex-row space-y-4 md:space-y-0 md:space-x-4">
<div className="bg-white rounded-lg shadow-md p-4 flex-1">
<h2 className="text-xl font-semibold mb-4 text-gray-700">URL Scrape/Screenshot</h2>
<div className="mb-2">
<label className="block text-gray-700 text-sm font-bold mb-2">Enter URL(s)</label>
<textarea
className="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline"
value={urlInput}
onChange={(e) => setUrlInput(e.target.value)}
/>
</div>
<div className="flex items-center space-x-4 mb-2">
<label className="block text-gray-700 text-sm font-bold">Bulk URLs</label>
<input type="checkbox" className="form-checkbox h-5 w-5 text-indigo-600" checked={bulkToggle} onChange={() => setBulkToggle(!bulkToggle)} />
</div>
<div className="mb-2">
<label className="block text-gray-700 text-sm font-bold mb-2">Select Action</label>
<div className="space-x-4 flex">
<label className="flex items-center space-x-2">
<input type="radio" className="form-radio h-4 w-4 text-indigo-600" value="Scrape data" checked={actionRadio === "Scrape data"} onChange={() => setActionRadio("Scrape data")} />
<span className="text-gray-700 text-sm">Scrape data</span>
</label>
<label className="flex items-center space-x-2">
<input type="radio" className="form-radio h-4 w-4 text-indigo-600" value="Capture image" checked={actionRadio === "Capture image"} onChange={() => setActionRadio("Capture image")} />
<span className="text-gray-700 text-sm">Capture image</span>
</label>
<label className="flex items-center space-x-2">
<input type="radio" className="form-radio h-4 w-4 text-indigo-600" value="Both" checked={actionRadio === "Both"} onChange={() => setActionRadio("Both")} />
<span className="text-gray-700 text-sm">Both</span>
</label>
</div>
</div>
<div className="mb-2">
<label className="block text-gray-700 text-sm font-bold mb-2">Max URLs to process</label>
<input type="range" className="form-range w-full" min={1} max={1000} value={maxUrls} onChange={(e) => setMaxUrls(parseInt(e.target.value))} />
<span className="text-sm text-gray-600">{maxUrls}</span>
</div>
<div className="mb-2">
<label className="block text-gray-700 text-sm font-bold mb-2">Crawl Depth</label>
<input type="range" className="form-range w-full" min={1} max={3} value={crawlDepth} onChange={(e) => setCrawlDepth(parseInt(e.target.value))} />
<span className="text-sm text-gray-600">{crawlDepth}</span>
</div>
<button
className={`bg-indigo-600 hover:bg-indigo-700 text-white font-bold py-2 px-4 rounded focus:outline-none focus:shadow-outline ${isProcessing ? 'opacity-50 cursor-not-allowed' : ''}`}
onClick={handleProcessUrls}
disabled={isProcessing}
>
{isProcessing ? 'Processing...' : 'Process URLs'}
</button>
{scrapedDataOutput && (
<div className="mt-4">
<label className="block text-gray-700 text-sm font-bold mb-2">Scraped Data</label>
<pre className="border border-gray-300 rounded-md bg-gray-50 p-2 overflow-auto max-h-48 whitespace-pre-wrap">
{scrapedDataOutput}
</pre>
</div>
)}
{screenshotOutput && (
<div className="mt-4">
<label className="block text-gray-700 text-sm font-bold mb-2">Screenshot</label>
<img src={screenshotOutput} alt="Screenshot" className="rounded-lg shadow-lg max-h-96" />
</div>
)}
{!screenshotOutput && actionRadio === 'Capture image' && (
<div className="mt-4">
<label className="block text-gray-700 text-sm font-bold mb-2">Screenshot</label>
<div className="bg-gray-200 border-2 border-dashed rounded-xl w-48 h-48 flex items-center justify-center">
<span className="text-gray-500">No image</span>
</div>
</div>
)}
</div>
<div className="bg-white rounded-lg shadow-md p-4 flex-1">
<h2 className="text-xl font-semibold mb-4 text-gray-700">Monitoring</h2>
<div className="mb-2">
<label className="block text-gray-700 text-sm font-bold mb-2">Enter URLs to Monitor (separated by newline)</label>
<textarea
className="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline"
value={monitorUrlInput}
onChange={(e) => setMonitorUrlInput(e.target.value)}
/>
</div>
<div className="mb-2">
<label className="block text-gray-700 text-sm font-bold mb-2">Monitoring Interval (seconds)</label>
<input type="range" className="form-range w-full" min={1} max={3600} value={intervalInput} onChange={(e) => setIntervalInput(parseInt(e.target.value))} />
<span className="text-sm text-gray-600">{intervalInput}</span>
</div>
<div className="flex space-x-4 mb-4">
<button
className={`bg-green-600 hover:bg-green-700 text-white font-bold py-2 px-4 rounded focus:outline-none focus:shadow-outline ${isMonitoring ? 'opacity-50 cursor-not-allowed' : ''}`}
onClick={handleStartMonitoring}
disabled={isMonitoring}
>
Start Monitoring
</button>
<button
className={`bg-red-600 hover:bg-red-700 text-white font-bold py-2 px-4 rounded focus:outline-none focus:shadow-outline ${!isMonitoring ? 'opacity-50 cursor-not-allowed' : ''}`}
onClick={handleStopMonitoring}
disabled={!isMonitoring}
>
Stop Monitoring
</button>
</div>
{changeOutput && (
<div className="mb-4">
<label className="block text-gray-700 text-sm font-bold mb-2">Monitoring Changes</label>
<pre className="border border-gray-300 rounded-md bg-gray-50 p-2 overflow-auto max-h-48 whitespace-pre-wrap">
{changeOutput}
</pre>
</div>
)}
{monitoringData.length > 0 && (
<div className="mb-4">
<label className="block text-gray-700 text-sm font-bold mb-2">Change History Graph</label>
<ResponsiveContainer width="100%" height={200}>
<LineChart data={monitoringData}>
<CartesianGrid strokeDasharray="3 3" />
<XAxis dataKey="time" />
<YAxis />
<Tooltip />
<Legend />
<Line type="monotone" dataKey="changes" stroke="#8884d8" />
</LineChart>
</ResponsiveContainer>
</div>
)}
<div className="mb-2">
<label className="block text-gray-700 text-sm font-bold mb-2">Monitoring Chat</label>
<div className="border rounded-md bg-gray-50 p-2 overflow-auto max-h-48 mb-2">
<ul className="space-y-2">
{chatHistory.map((msg, index) => (
<li key={index} className={msg.role === 'user' ? 'text-right' : 'text-left'}>
<div className={`${msg.role === 'user' ? 'bg-indigo-100' : 'bg-gray-100'} inline-block rounded-md p-2`}>
<span className="font-bold text-gray-700">{msg.role === 'user' ? 'You' : 'System'}:</span> <span className="text-gray-800">{msg.content}</span>
</div>
</li>
))}
</ul>
</div>
<input
type="text"
placeholder="Type command"
className="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline"
onKeyDown={(e) => {
if (e.key === 'Enter') {
handleChatSubmit(e.target.value);
(e.target as HTMLInputElement).value = '';
}
}}
/>
</div>
</div>
</div>
</div>
);
};
export default App;