mirror of
https://github.com/wahyd4/hey-search.git
synced 2026-08-08 21:05:14 +10:00
Add screenshots
This commit is contained in:
@@ -1,8 +1,30 @@
|
||||
# HeySearch
|
||||
|
||||
A privacy-respecting [metasearch engine](https://en.wikipedia.org/wiki/Metasearch_engine) that aggregates results from multiple search engines.
|
||||
A privacy-respecting [metasearch engine](https://en.wikipedia.org/wiki/Metasearch_engine) that aggregates results from multiple search engines — with a modern UI designed for humans and AI agents alike.
|
||||
|
||||
Inspired by [SearXNG](https://github.com/searxng/searxng).
|
||||
> Inspired by [SearXNG](https://github.com/searxng/searxng), built for the modern web.
|
||||
|
||||
## Screenshots
|
||||
|
||||
| Home | Web Search | Image Search |
|
||||
|------|-----------|--------------|
|
||||
| [](screenshots/home-page.png) | [](screenshots/text-search.png) | [](screenshots/image-search.png) |
|
||||
|
||||
## Why HeySearch over SearXNG?
|
||||
|
||||
Both are open-source, self-hosted, privacy-respecting metasearch engines. Here's why HeySearch is the better choice for most people:
|
||||
|
||||
| | HeySearch | SearXNG |
|
||||
|---|---|---|
|
||||
| **Setup** | `docker run -p 8000:8000 ghcr.io/…/hey-search` — one command, zero config | Requires YAML config, engine tuning, often breaks out of the box |
|
||||
| **UI** | Modern, clean React UI with dark mode, background images, image lightbox | Functional but dated — not optimised for mobile or daily use |
|
||||
| **AI agent friendly** | Clean JSON REST API, OpenAPI docs at `/docs`, designed to be queried programmatically | API exists but less documented; HTML-heavy responses |
|
||||
| **Bookmarks** | Built-in bookmark manager for results | ❌ |
|
||||
| **Search history** | Full search history with timestamps, re-run any past query in one click | ❌ |
|
||||
| **Usage stats** | Built-in analytics dashboard — top queries, click-through rates, engine usage | ❌ |
|
||||
| **Background gallery** | Beautiful Unsplash/Picsum backgrounds on the home page | ❌ |
|
||||
|
||||
**TL;DR** — If you want something you can run in 30 seconds, looks great, works well on your phone, and exposes a clean API for your AI tools, HeySearch is for you. If you need 70+ search engines and deep customisation, SearXNG has the edge.
|
||||
|
||||
## Architecture
|
||||
|
||||
@@ -19,36 +41,7 @@ Inspired by [SearXNG](https://github.com/searxng/searxng).
|
||||
|
||||
## Running Locally
|
||||
|
||||
### 1. Backend
|
||||
|
||||
```bash
|
||||
cd backend
|
||||
uv sync # install dependencies
|
||||
uv run uvicorn app.main:app --reload --port 8000
|
||||
```
|
||||
|
||||
To enable Redis caching:
|
||||
|
||||
```bash
|
||||
REDIS_URL=redis://192.168.1.2:6399 uv run uvicorn app.main:app --reload --port 8000
|
||||
```
|
||||
|
||||
The API will be available at `http://localhost:8000`. Interactive docs:
|
||||
|
||||
- Swagger UI: http://localhost:8000/docs
|
||||
- Redoc: http://localhost:8000/redoc
|
||||
|
||||
### 2. Frontend
|
||||
|
||||
```bash
|
||||
cd frontend
|
||||
npm install # install dependencies
|
||||
npm run dev # start dev server with hot reload
|
||||
```
|
||||
|
||||
The frontend dev server runs at `http://localhost:5173` and proxies `/api` requests to the backend.
|
||||
|
||||
### 3. Quick start (both)
|
||||
### 1. Quick start
|
||||
|
||||
```bash
|
||||
# Without Redis (caching disabled)
|
||||
@@ -58,7 +51,7 @@ just dev
|
||||
REDIS_URL=redis://192.168.1.2:6399 just dev
|
||||
```
|
||||
|
||||
### 4. Docker (production)
|
||||
### 2. Docker (production)
|
||||
|
||||
```bash
|
||||
docker build -t hey-search .
|
||||
|
||||
@@ -557,7 +557,7 @@ function App() {
|
||||
|
||||
{/* Category / filter bar */}
|
||||
<div className="sticky top-[57px] z-30 border-b bg-background/95 backdrop-blur">
|
||||
<nav aria-label="Search categories" className="flex items-center gap-1 px-4 py-2">
|
||||
<nav aria-label="Search categories" className="flex items-center gap-1 px-4 py-2 overflow-x-auto [&::-webkit-scrollbar]:hidden [-ms-overflow-style:none] [scrollbar-width:none]">
|
||||
{([
|
||||
{ key: "web" as const, label: "Web", icon: Globe },
|
||||
{ key: "images" as const, label: "Images", icon: ImageIcon },
|
||||
@@ -567,7 +567,7 @@ function App() {
|
||||
onClick={() => handleCategoryChange(key)}
|
||||
aria-current={category === key ? "page" : undefined}
|
||||
className={cn(
|
||||
"flex items-center gap-1.5 rounded-full px-4 py-1.5 text-sm font-medium transition-colors focus-visible:ring-2 focus-visible:ring-ring focus-visible:outline-none",
|
||||
"shrink-0 flex items-center gap-1.5 rounded-full px-4 py-1.5 text-sm font-medium transition-colors focus-visible:ring-2 focus-visible:ring-ring focus-visible:outline-none",
|
||||
category === key
|
||||
? "bg-primary text-primary-foreground"
|
||||
: "text-muted-foreground hover:bg-accent"
|
||||
@@ -581,15 +581,15 @@ function App() {
|
||||
{/* Image size filter — only visible in images category */}
|
||||
{category === "images" && (
|
||||
<>
|
||||
<div className="mx-2 h-5 w-px bg-border" aria-hidden="true" />
|
||||
<SlidersHorizontal className="h-3.5 w-3.5 text-muted-foreground" aria-hidden="true" />
|
||||
<div className="mx-2 h-5 w-px shrink-0 bg-border" aria-hidden="true" />
|
||||
<SlidersHorizontal className="h-3.5 w-3.5 shrink-0 text-muted-foreground" aria-hidden="true" />
|
||||
{IMAGE_SIZE_OPTIONS.map(({ value, label }) => (
|
||||
<button
|
||||
key={value}
|
||||
onClick={() => handleImageSizeChange(value)}
|
||||
aria-pressed={imageSize === value}
|
||||
className={cn(
|
||||
"rounded-full px-3 py-1 text-xs font-medium transition-colors focus-visible:ring-2 focus-visible:ring-ring focus-visible:outline-none",
|
||||
"shrink-0 rounded-full px-3 py-1 text-xs font-medium transition-colors focus-visible:ring-2 focus-visible:ring-ring focus-visible:outline-none",
|
||||
imageSize === value
|
||||
? "bg-secondary text-secondary-foreground"
|
||||
: "text-muted-foreground hover:bg-accent"
|
||||
@@ -603,15 +603,15 @@ function App() {
|
||||
|
||||
{/* Sort order — available for both web and images */}
|
||||
<>
|
||||
<div className="mx-2 h-5 w-px bg-border" aria-hidden="true" />
|
||||
<span className="text-xs text-muted-foreground" aria-hidden="true">Sort:</span>
|
||||
<div className="mx-2 h-5 w-px shrink-0 bg-border" aria-hidden="true" />
|
||||
<span className="shrink-0 text-xs text-muted-foreground" aria-hidden="true">Sort:</span>
|
||||
{SORT_OPTIONS.map(({ value, label }) => (
|
||||
<button
|
||||
key={value}
|
||||
onClick={() => handleSortChange(value)}
|
||||
aria-pressed={sortOrder === value}
|
||||
className={cn(
|
||||
"rounded-full px-3 py-1 text-xs font-medium transition-colors focus-visible:ring-2 focus-visible:ring-ring focus-visible:outline-none",
|
||||
"shrink-0 rounded-full px-3 py-1 text-xs font-medium transition-colors focus-visible:ring-2 focus-visible:ring-ring focus-visible:outline-none",
|
||||
sortOrder === value
|
||||
? "bg-secondary text-secondary-foreground"
|
||||
: "text-muted-foreground hover:bg-accent"
|
||||
|
||||
@@ -106,7 +106,8 @@ export function AppHeader({
|
||||
className="shrink-0 text-xl font-bold focus-visible:ring-2 focus-visible:ring-ring focus-visible:outline-none rounded"
|
||||
style={{ color: "#27ae60" }}
|
||||
>
|
||||
HeySearch
|
||||
<span className="sm:hidden">HS</span>
|
||||
<span className="hidden sm:inline">HeySearch</span>
|
||||
</button>
|
||||
)}
|
||||
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 3.1 MiB |
Binary file not shown.
|
After Width: | Height: | Size: 1.1 MiB |
Binary file not shown.
|
After Width: | Height: | Size: 386 KiB |
Reference in New Issue
Block a user