Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 7 additions & 25 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,23 +1,3 @@
FROM node:lts AS llama-builder

ARG LLAMA_CPP_RELEASE_TAG="b6604"

RUN apt-get update && apt-get install -y \
build-essential \
cmake \
ccache \
git \
curl

RUN cd /tmp && \
git clone https://github.com/ggerganov/llama.cpp.git && \
cd llama.cpp && \
git checkout $LLAMA_CPP_RELEASE_TAG && \
cmake -B build -DGGML_NATIVE=OFF -DLLAMA_CURL=OFF && \
cmake --build build --config Release -j --target llama-server && \
mkdir -p /usr/local/lib/llama && \
find build -type f \( -name "libllama.so" -o -name "libmtmd.so" -o -name "libggml.so" -o -name "libggml-base.so" -o -name "libggml-cpu.so" \) -exec cp {} /usr/local/lib/llama/ \;

FROM node:lts

ARG SEARXNG_COMMIT_SHA="6da6eee265daeb4a62ab638d6921522bf405de69"
Expand Down Expand Up @@ -60,10 +40,6 @@ RUN chmod 644 $SEARXNG_SETTINGS_PATH && \
/usr/local/searxng/searxng-venv/bin/pip install -r requirements.txt && \
/usr/local/searxng/searxng-venv/bin/pip install --no-build-isolation -e .

COPY --from=llama-builder /tmp/llama.cpp/build/bin/llama-server /usr/local/bin/
COPY --from=llama-builder /usr/local/lib/llama/* /usr/local/lib/
RUN ldconfig /usr/local/lib

USER ${USERNAME}

WORKDIR ${APP_DIR}
Expand All @@ -86,7 +62,13 @@ RUN npm ci

COPY --chown=${USERNAME}:${USERNAME} . .

RUN git config --global --add safe.directory ${APP_DIR} && \
# The commit hash is optional build metadata, so a build context without a
# usable repository must not fail the build. This happens when building from a
# git worktree, where `.git` is a file pointing at a gitdir outside the context;
# git then treats every command as fatal, including `config --global`.
RUN git config --global --add safe.directory ${APP_DIR} 2>/dev/null || true; \
git rev-parse --short HEAD >/dev/null 2>&1 || \
echo "WARNING: no usable git repository in the build context, so the app will report an empty commit hash."; \
npm run build

HEALTHCHECK --interval=5m CMD curl -f http://localhost:7860/status || exit 1
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ flowchart LR
subgraph container [Docker container]
Server[App server]
SearXNG[SearXNG<br/>metasearch]
Reranker[Reranker<br/>llama-server]
Reranker[Reranker<br/>ONNX Runtime]
end
UI <--> Storage
UI -->|search| Server
Expand Down
8 changes: 4 additions & 4 deletions agents.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ This is your navigation hub. Start here, follow the links, and return when you n
- **`docs/ui-components.md`** - Component architecture and PubSub patterns
- **`docs/search-history.md`** - History database schema and management
- **`docs/conversation-memory.md`** - Token budgeting and rolling summaries
- **`docs/reranking.md`** - Reranker subsystem and llama-server lifecycle
- **`docs/reranking.md`** - Reranker subsystem and model lifecycle
- **`docs/glossary.md`** - Codebase-specific terms and domain concepts

### Development
Expand Down Expand Up @@ -128,19 +128,19 @@ Need to:
- `server/internalApiEndpointServerHook.ts` - `/inference` proxy to self-hosted API
- `server/validateAccessKeyServerHook.ts` - Access key validation endpoint
- `server/statusEndpointServerHook.ts` - `/status` health check endpoint
- `server/rerankerServiceHook.ts` - llama-server lifecycle management for reranking
- `server/rerankerServiceHook.ts` - Reranker model lifecycle management
- `server/compressionServerHook.ts` - gzip/brotli compression for responses
- `server/crossOriginServerHook.ts` - COOP/COEP headers for SharedArrayBuffer
- `server/cacheServerHook.ts` - Cache-Control headers (preview server only)
- `server/webSearchService.ts` - SearXNG integration with circuit breaker and retry logic
- `server/rerankerService.ts` - Reranker service (llama-server process management)
- `server/rerankerService.ts` - Reranker service (ONNX Runtime inference)
- `server/rankSearchResults.ts` - Score-based filtering and result reordering
- `server/searchToken.ts` - CSRF token generation and storage
- `server/verifiedTokens.ts` - In-memory `Set<string>` of verified session tokens
- `server/verifyTokenAndRateLimit.ts` - Token verification and rate limiting
- `server/handleTokenVerification.ts` - Search token validation logic
- `server/searchesSinceLastRestart.ts` - In-memory search counters for analytics
- `server/downloadFileFromHuggingFaceRepository.ts` - Downloads GGUF models from HuggingFace
- `server/downloadFileFromHuggingFaceRepository.ts` - Downloads model files from HuggingFace

### Hooks
- `client/hooks/useSearchHistory.ts` - Search history management from IndexedDB
Expand Down
15 changes: 15 additions & 0 deletions client/modules/appInfo.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { describe, expect, it } from "vitest";
import { appVersion } from "@/modules/appInfo";

describe("appVersion", () => {
it("appends the commit hash as semver build metadata when the build had one", () => {
// vitest.config.ts defines VITE_COMMIT_SHORT_HASH as "test-hash".
expect(appVersion).toMatch(/^\d{4}\.\d{1,2}\.\d{1,2}\+test-hash$/);
});

it("never leaves a dangling separator", () => {
// A build context without a usable git repository yields an empty hash, and
// "1.2.3+" is not a valid version string.
expect(appVersion.endsWith("+")).toBe(false);
});
});
10 changes: 8 additions & 2 deletions client/modules/appInfo.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,12 @@ export const appName = repository.url.split("/").pop();
*/
export const appRepository = repository.url;
/**
* Application version with build timestamp and commit hash
* Application version with build timestamp and, when the build had a git
* repository available, the commit hash as semver build metadata.
*/
export const appVersion = `${getSemanticVersion(VITE_BUILD_DATE_TIME)}+${VITE_COMMIT_SHORT_HASH}`;
export const appVersion = [
getSemanticVersion(VITE_BUILD_DATE_TIME),
VITE_COMMIT_SHORT_HASH,
]
.filter(Boolean)
.join("+");
7 changes: 2 additions & 5 deletions docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -197,16 +197,13 @@ Same structure but without volume mounts and with pre-built assets.

### Dockerfile Environment

The Dockerfile sets up:
1. **Builder stage**: Compiles `llama-server` from llama.cpp
2. **Runtime stage**:
The Dockerfile sets up a single runtime stage:
- Node.js LTS
- Python 3 + SearXNG
- llama-server binary

The app runs under the `node` user, with the app directory at `/home/node/app`. The production image starts the app with `npm start -- --host` (i.e. `vite preview`), not `npm run dev`.

**Multi-service container** runs all three concurrently via shell process composition.
**Multi-service container** runs SearXNG and Node.js concurrently via shell process composition.

## Vite Environment Injection

Expand Down
5 changes: 2 additions & 3 deletions docs/development-commands.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

## Docker

- **`docker compose up`**: Development environment with SearXNG, llama-server, and Node.js
- **`docker compose up`**: Development environment with SearXNG and Node.js
- **`docker compose -f docker-compose.production.yml up --build`**: Production deployment

## Testing
Expand Down Expand Up @@ -70,8 +70,7 @@ Used by both `on-push-to-main` and `on-pull-request-to-main` to run the producti
### Docker Image Builds

The Docker image uses a multi-stage build:
1. **Builder stage** (`llama-builder`): Compiles llama-server from llama.cpp source, extracts shared libraries (`libllama.so`, `libmtmd.so`, `libggml.so`, etc.)
2. **Runtime stage**: Installs Python/SearXNG, copies llama-server binaries, builds the Vite frontend, runs SearXNG and Node.js in a single container via shell process composition
The image installs Python/SearXNG, builds the Vite frontend, and runs SearXNG and Node.js in a single container via shell process composition. No compilation step is required: the reranker's ONNX Runtime binaries ship prebuilt with the npm dependency.

The production image is published to `ghcr.io` with multi-platform support (linux/amd64, linux/arm64). Tags and labels are auto-generated from Git metadata via `docker/metadata-action`.

Expand Down
6 changes: 3 additions & 3 deletions docs/glossary.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,9 @@ Instead of a heavy state management library like Redux, MiniSearch uses a minima

### Reranker

A secondary search stage that takes initial results from SearXNG and re-orders them based on relevance to the query using a cross-encoder model (`jina-reranker-v1-tiny-en`) running on a local `llama-server` instance.
A secondary search stage that takes initial results from SearXNG and re-orders them based on relevance to the query using a cross-encoder model (`jina-reranker-v1-tiny-en`) running in-process via ONNX Runtime.

- **Implementation**: Spawns `llama-server` child process with `--reranking` and `--pooling rank` flags
- **Implementation**: Loads the model's ONNX export with `onnxruntime-node`; no child process
- **Health Check**: Polls `/health` endpoint via `getRerankerStatus`
- **Scoring**: Results filtered using standard deviation thresholds (`kStandardDeviationFactor = 0.3`)
- **Fallback**: If reranker is unhealthy, returns unranked SearXNG results
Expand Down Expand Up @@ -95,7 +95,7 @@ Middleware registered via Vite plugin hooks (`configureServer`, `configurePrevie
| `cacheServerHook` | Cache-Control headers |
| `validateAccessKeyServerHook` | Access key validation |
| `internalApiEndpointServerHook` | `/inference` proxy |
| `rerankerServiceHook` | llama-server lifecycle management |
| `rerankerServiceHook` | Reranker model lifecycle management |

### Circuit Breaker

Expand Down
10 changes: 5 additions & 5 deletions docs/overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,10 @@ The application has three primary entry points:

The Docker container runs three services concurrently:
- **SearXNG** - Privacy-focused metasearch engine
- **llama-server** - Local AI inference server
- **ONNX Runtime** - In-process inference for result reranking
- **Node.js application** - Main application server

The multi-stage build process first compiles llama-server from source, then creates the final runtime image with Node.js and Python environments. The container entrypoint starts SearXNG in the background and then launches the Node.js application.
The build creates a runtime image with Node.js and Python environments. The container entrypoint starts SearXNG in the background and then launches the Node.js application.

## State Management Architecture

Expand Down Expand Up @@ -76,7 +76,7 @@ The system supports two operational modes:
- Vite preview server (no HMR)
- Optimized bundle with minification

Both modes run the same underlying services (SearXNG, llama-server) but differ in how the frontend is served and rebuilt.
Both modes run the same underlying services (SearXNG, the reranker) but differ in how the frontend is served and rebuilt.

## Search and AI Integration Flow

Expand All @@ -102,7 +102,7 @@ The system executes two parallel flows when a user submits a query:
5. Response updates throttled to ~12 updates/sec via `throttleit` to prevent React render overload
6. Response saved to history database via `saveLlmResponseForQuery`

The `textGeneration` module orchestrates the entire search-to-response flow, managing search requests, LLM context preparation, and response streaming. Search results are optionally reranked using a local llama-server instance before being passed to the LLM for response generation.
The `textGeneration` module orchestrates the entire search-to-response flow, managing search requests, LLM context preparation, and response streaming. Search results are optionally reranked in-process via ONNX Runtime before being passed to the LLM for response generation.

### Web Search Service Reliability

Expand Down Expand Up @@ -192,7 +192,7 @@ MiniSearch implements all server-side logic as Vite plugin hooks. Each hook regi
| `cacheServerHook` | `server/cacheServerHook.ts` | Cache-Control headers (preview only) |
| `validateAccessKeyServerHook` | `server/validateAccessKeyServerHook.ts` | Access key validation endpoint |
| `internalApiEndpointServerHook` | `server/internalApiEndpointServerHook.ts` | `/inference` proxy to self-hosted API |
| `rerankerServiceHook` | `server/rerankerServiceHook.ts` | llama-server lifecycle management for result reranking |
| `rerankerServiceHook` | `server/rerankerServiceHook.ts` | Reranker model lifecycle management for result reranking |

Key server-side modules:

Expand Down
2 changes: 1 addition & 1 deletion docs/quick-start.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ Production mode:

The Docker container runs three services concurrently:
- **SearXNG** - Privacy-focused metasearch engine (starts in background)
- **llama-server** - Local AI inference server (for result reranking)
- **ONNX Runtime** - In-process inference for result reranking
- **Node.js (Vite)** - Main application server

### Startup Sequence
Expand Down
Loading
Loading