Skip to content

Commit 18dda0f

Browse files
committed
readme and ui finalization
1 parent c78034b commit 18dda0f

4 files changed

Lines changed: 37 additions & 16 deletions

File tree

‎README.md‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
Crawler Project
2-
2+
Github link: https://github.com/TalhaMudo/The-Great-Web-Heist-of-Talha
33
This project implements a single-machine web crawler and keyword search engine with a live dashboard. It is designed to satisfy the assignment requirements around:
44

55
- indexing from an origin URL up to depth `k`,
@@ -65,7 +65,7 @@ The dashboard allows:
6565
### Index/Search
6666

6767
- `POST /index`
68-
Starts a new crawl job with origin URL, depth `k`, and optional initial rate limit.
68+
Starts a new crawl job with origin URL, depth `k`, optional `max_urls_to_visit`, and optional initial rate limit.
6969

7070
- `GET /search?query=...&limit=...`
7171
Returns relevant indexed URLs.

‎crawler.db‎

9.19 MB
Binary file not shown.

‎frontend/src/App.tsx‎

Lines changed: 32 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -89,9 +89,9 @@ type EmbeddingStatus = {
8989
export const App: React.FC = () => {
9090
const [viewMode, setViewMode] = useState<"crawler" | "search" | "embeddings">("crawler");
9191
const [origin, setOrigin] = useState("");
92-
const [depth, setDepth] = useState(2);
92+
const [depthInput, setDepthInput] = useState("2");
9393
const [maxUrlsToVisit, setMaxUrlsToVisit] = useState("500");
94-
const [rateLimit, setRateLimit] = useState(1.0);
94+
const [rateLimitInput, setRateLimitInput] = useState("1.0");
9595
const [currentJobId, setCurrentJobId] = useState<string | null>(null);
9696
const [selectedJobId, setSelectedJobId] = useState<string | null>(null);
9797
const [selectedJob, setSelectedJob] = useState<JobDetail | null>(null);
@@ -184,15 +184,36 @@ export const App: React.FC = () => {
184184
const startIndex = async () => {
185185
setError(null);
186186
try {
187+
const trimmedDepth = depthInput.trim();
188+
const parsedDepth = Number(trimmedDepth);
189+
if (!trimmedDepth || !Number.isInteger(parsedDepth) || parsedDepth < 0 || parsedDepth > 8) {
190+
throw new Error("Max depth (k) must be an integer between 0 and 8.");
191+
}
192+
193+
const trimmedRate = rateLimitInput.trim();
194+
const parsedRateLimit = Number(trimmedRate);
195+
if (!trimmedRate || !Number.isFinite(parsedRateLimit) || parsedRateLimit <= 0) {
196+
throw new Error("Crawler speed (req/s) must be a positive number.");
197+
}
198+
199+
const trimmedMaxUrls = maxUrlsToVisit.trim();
200+
const parsedMaxUrls = trimmedMaxUrls ? Number(trimmedMaxUrls) : null;
201+
if (
202+
trimmedMaxUrls &&
203+
(!Number.isInteger(parsedMaxUrls) || parsedMaxUrls === null || parsedMaxUrls <= 0)
204+
) {
205+
throw new Error("Max URLs to Visit must be a positive integer.");
206+
}
207+
187208
setIsIndexing(true);
188209
const res = await fetch("/index", {
189210
method: "POST",
190211
headers: { "Content-Type": "application/json" },
191212
body: JSON.stringify({
192213
origin,
193-
k: depth,
194-
max_urls_to_visit: maxUrlsToVisit ? Number(maxUrlsToVisit) : null,
195-
rate_limit_per_sec: rateLimit,
214+
k: parsedDepth,
215+
max_urls_to_visit: parsedMaxUrls,
216+
rate_limit_per_sec: parsedRateLimit,
196217
}),
197218
});
198219
if (!res.ok) {
@@ -401,7 +422,7 @@ export const App: React.FC = () => {
401422
</div>
402423
{!query && (
403424
<p className="hint">
404-
Enter a query to search. Your crawled and embedded pages are ready, but search needs at least one keyword.
425+
Enter a query to search.
405426
</p>
406427
)}
407428
<div className="dual-results-grid">
@@ -444,7 +465,7 @@ export const App: React.FC = () => {
444465
<h3>Semantic Search</h3>
445466
<div className="results">
446467
{semanticResults.length === 0 ? (
447-
<p className="hint">No semantic results yet. Run the embedding engine first.</p>
468+
<p className="hint">No semantic results yet. Run the embedding engine first if not.</p>
448469
) : (
449470
<table className="search-table">
450471
<thead>
@@ -623,8 +644,8 @@ export const App: React.FC = () => {
623644
type="number"
624645
min={0}
625646
max={8}
626-
value={depth}
627-
onChange={(e) => setDepth(Number(e.target.value))}
647+
value={depthInput}
648+
onChange={(e) => setDepthInput(e.target.value)}
628649
/>
629650
</label>
630651
<label className="field">
@@ -644,8 +665,8 @@ export const App: React.FC = () => {
644665
type="number"
645666
min={0.1}
646667
step={0.1}
647-
value={rateLimit}
648-
onChange={(e) => setRateLimit(Number(e.target.value))}
668+
value={rateLimitInput}
669+
onChange={(e) => setRateLimitInput(e.target.value)}
649670
/>
650671
</label>
651672
<button onClick={startIndex} disabled={!origin || isIndexing}>

‎product_prd.md‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ Building search engines is usually out of reach for a single engineer, but AI-as
1919

2020
- Internet-scale crawling, cross-machine distribution, or strict guarantees about completeness of the web graph.
2121
- Production-grade search relevance (no BM25, no learning-to-rank; a simple token-based heuristic is sufficient).
22-
- Full politeness policies (robots.txt, per-domain rate scheduling); we only implement a global rate limiter.
22+
- Full politeness policies (robots.txt, per-domain rate scheduling); the current implementation focuses on global queue limiting plus per-job rate controls.
2323
- Strong security hardening (sandboxing, HTML sanitization) beyond what’s necessary for a localhost classroom demo.
2424

2525
#### User stories
@@ -30,15 +30,15 @@ Building search engines is usually out of reach for a single engineer, but AI-as
3030
- **As a student**, I can monitor the dashboard to understand how queue depth, processed URL count, and backpressure state change over time as the crawler runs.
3131
- **As a student**, I can restart the backend and immediately search previously indexed pages because they were persisted locally.
3232
- **As a student**, I can open an Embeddings page, tune embedding speed/max pages, and monitor `% embedded` in real time.
33-
- **As an instructor**, I can point at the dashboard and code to explain concurrency (worker pool, queue), backpressure (bounded queue), and uniqueness (visited set).
33+
- **As an instructor**, I can point at the dashboard and code to explain concurrency (worker pool, queue), backpressure (global queue limit), and uniqueness (visited set).
3434

3535
#### Functional requirements
3636

3737
- **Index endpoint**
3838
- `POST /index` accepts `{ origin: string, k: number }`.
3939
- Spawns an asynchronous crawl job identified by `job_id`.
4040
- Enforces depth limit `k` and “never crawl the same URL twice per job” using a normalized URL visited set.
41-
- Applies global backpressure using a bounded `asyncio.Queue` and a fixed worker count; when the queue is full, link discovery pauses and `backpressure_state` transitions to `"queue_full"`.
41+
- Applies global backpressure using a shared global queue limit and a fixed worker count; when the limit is reached, link discovery pauses and `backpressure_state` transitions to `"queue_full"`.
4242

4343
- **Search endpoint**
4444
- `GET /search?query=...` returns a list of results, each containing `(relevant_url, origin_url, depth, score, title)`.

0 commit comments

Comments
 (0)