Spaces:
Running
Running
Sync from felladrin/MiniSearch@a00aebb
Browse files- .env.example +3 -0
- .github/workflows/publish-docker-image.yml +1 -1
- changelog.md +8 -0
- client/modules/textGeneration.ts +17 -3
- docs/configuration.md +8 -0
- docs/overview.md +8 -7
- docs/page-content.md +18 -3
- docs/reranking.md +43 -2
- package-lock.json +66 -67
- package.json +1 -0
.env.example
CHANGED
|
@@ -37,3 +37,6 @@ SEARCH_FALLBACK_PARALLEL_API_KEY=""
|
|
| 37 |
|
| 38 |
# The API key for You.com. Optional; without it the keyless "profile=free" endpoint is used, which allows 100 queries per day.
|
| 39 |
SEARCH_FALLBACK_YOUCOM_API_KEY=""
|
|
|
|
|
|
|
|
|
|
|
|
| 37 |
|
| 38 |
# The API key for You.com. Optional; without it the keyless "profile=free" endpoint is used, which allows 100 queries per day.
|
| 39 |
SEARCH_FALLBACK_YOUCOM_API_KEY=""
|
| 40 |
+
|
| 41 |
+
# Whether a search that asks for an official page may run a second, "site:"-scoped query to reach the official site itself. Off by default, since it sends the engines a second request; only "true" or "1" turn it on.
|
| 42 |
+
SEARCH_OFFICIAL_LANE_ENABLED="false"
|
.github/workflows/publish-docker-image.yml
CHANGED
|
@@ -147,7 +147,7 @@ jobs:
|
|
| 147 |
id: record-commit
|
| 148 |
run: echo "sha=$(git rev-parse HEAD)" >> "$GITHUB_OUTPUT"
|
| 149 |
- name: Log in to the Container registry
|
| 150 |
-
uses: docker/login-action@
|
| 151 |
with:
|
| 152 |
registry: ${{ env.REGISTRY }}
|
| 153 |
username: ${{ github.actor }}
|
|
|
|
| 147 |
id: record-commit
|
| 148 |
run: echo "sha=$(git rev-parse HEAD)" >> "$GITHUB_OUTPUT"
|
| 149 |
- name: Log in to the Container registry
|
| 150 |
+
uses: docker/login-action@8aa5831fb8d48d8f76594c7ddde61ae58019f550 # v4
|
| 151 |
with:
|
| 152 |
registry: ${{ env.REGISTRY }}
|
| 153 |
username: ${{ github.actor }}
|
changelog.md
CHANGED
|
@@ -3,6 +3,14 @@
|
|
| 3 |
Notable changes, grouped by date, most recent first.
|
| 4 |
|
| 5 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 6 |
## 2026-11-03
|
| 7 |
|
| 8 |
- The five thinking models added on 2026-11-02 (Spark X2.6 1.7B and 4B, Granite 4.2 3B, Qwen 3.5 4B, Gemma 4 E2B) no longer ship with thinking switched off. Their chain of thought arrives on wllama's `reasoning_content` channel and is now wrapped in the configured reasoning markers, so the existing thinking UI shows it while the model reasons and the answer streams in after. The model warmup counts a reasoning token as a working model, so thinking models no longer log a failed warmup.
|
|
|
|
| 3 |
Notable changes, grouped by date, most recent first.
|
| 4 |
|
| 5 |
|
| 6 |
+
## 2026-11-05
|
| 7 |
+
|
| 8 |
+
- Relative month and year words in a query are anchored to absolute ones before the search leaves the browser: "next month" reaches the engines as "December 2026" and "ๆๅนด" as "2027ๅนด". The anchor is appended rather than substituted, so a query that already names a year keeps its own phrasing, and two anchors per query is the ceiling. The typed query still drives the prompt, the UI, and the history entry; only the copy sent to the search engines changes, and the search cache key changes with it. Anchors skip titles and phrases that are not dates ("ๆๅนดไปๆฅ", "็ปไธๆ็", "the last year of college") and leave "recent" and "latest" alone, because recency is what the engines already rank for. Day words ("tomorrow", "ๆๅคฉ") stay as typed: their anchor would be an ISO date, which pages rarely write, and many queries use them as names ("usa today"). Only English and Chinese phrases are recognized. `shared/relativeDates.ts` holds the rules and `client/modules/textGeneration.ts` applies them.
|
| 9 |
+
- The page reader drops structural navigation before it extracts text: `nav`, `aside`, `header`, `footer` and `form` blocks go, and so does any block whose text is mostly links (60% or more, in blocks of 120 characters and up), judged innermost-first so a page-wide wrapper cannot take the article down with it. Inside an `<article>`, a `<main>` or a `role="main"` block, link density removes nothing, because a list of linked titles there is usually the answer itself. The page's title and h1/h2 words also join the passage score at 0.15 weight, so a page whose heading names the query breaks ties against its own navigation. On the sortiraparis December page the extracted text fell from 57,160 to 2,128 characters and the answering passage moved from 10th of 61 to 3rd of 8, which is what puts it inside a page's excerpt share. A link-list home page now contributes far less body text, and the reader accepts that trade: a thin excerpt beats one made of navigation links.
|
| 10 |
+
- A result from the site the query names gets a bonus on its semantic score before the score filter: +0.5 when a query word equals the domain's label ("disney" and disney.com, subdomains included), +0.75 when the label is the query's own words joined ("disney plus" and disneyplus.com, "coca cola" and coca-cola.com). The bonus is added rather than multiplied because the reranker returns a raw logit that is usually negative, where a factor above one would demote the named site instead of promoting it. Substrings do not count (disneyfan.com gets nothing), filler words never match on their own (plus.com, news.com), and a query that names a year will not promote a page that states a different year, so a brand's recap of last year's event cannot displace fresher reporting; a page that states no year at all keeps the bonus, because relative-time queries are anchored to a year before they reach the search. A label that spells only the start of the query leaves words unused and gets nothing (`tesla investor relations` and teslainvestor.com), a label that starts with a filler word is skipped (`the disney store` and thedisney.com), a split label counts from four characters, and a trailing dot on the hostname changes nothing. The site's name comes from the public suffix list, private section included (the `tldts` package), so bbc.co.uk answers "bbc" and a tenant of a shared host such as someone.github.io answers its own name, not "github". The pinned top result is never boosted, so the score spread the client reads relevance tags from keeps its shape. The reranker still never sees the URL, and a result the search did not return cannot be added here: the boost reorders and re-selects what the pipeline already holds, which is enough to move a page into the six the model reads. `server/authorityRanking.ts` holds the rule and `docs/reranking.md` records it.
|
| 11 |
+
- A text search that asks for an official page can now reach the official site even when the engine's results never included it, behind a new setting, `SEARCH_OFFICIAL_LANE_ENABLED`, which is off by default because it sends the engines a second request. When it is on, the query carries navigation intent (`ๅฎ็ฝ`, `ๆ็`, `่ฅไธๆถ้ด`, `official`, `hours`, `showtimes` and the like, in Chinese and English only), and one of the returned results names a site, the server sends a second, `site:`-scoped query to SearXNG and merges up to three of its results, deduplicated by URL and kept to that registrable domain, behind the pinned first result: the ranking re-sorts only what follows the top result, so a page appended past that window would never be re-sorted at all. The domain has to come from the results themselves, by the same rule the score bonus uses, because guessing `<token>.com` from the query would turn a search box into a scanner; a query that names a brand whose official site is absent from every result gets no second query. The second query is one request with no retry, and its failures count toward neither the SearXNG circuit nor the engine health in `/status`, so it cannot slow down or degrade the searches users are waiting on. SearXNG still sees the extra request, so an engine that suspends the instance after a CAPTCHA does so for every search. It runs only when SearXNG served the search (never after the fallback), never uses the last five seconds of the search deadline, and is canceled at eight seconds; a failure only logs and is counted, so it can add results but never turn a served search into a 502. `/status` reports `searches.officialLane` (searches, `resultsAdded`, `failures`), and `server/officialLane.ts` holds the rule.
|
| 12 |
+
|
| 13 |
+
|
| 14 |
## 2026-11-03
|
| 15 |
|
| 16 |
- The five thinking models added on 2026-11-02 (Spark X2.6 1.7B and 4B, Granite 4.2 3B, Qwen 3.5 4B, Gemma 4 E2B) no longer ship with thinking switched off. Their chain of thought arrives on wllama's `reasoning_content` channel and is now wrapped in the configured reasoning markers, so the existing thinking UI shows it while the model reasons and the answer streams in after. The model warmup counts a reasoning token as a working model, so thinking models no longer log a failed warmup.
|
client/modules/textGeneration.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
|
|
| 1 |
import gptTokenizer from "gpt-tokenizer";
|
| 2 |
import prettyMilliseconds from "pretty-ms";
|
| 3 |
import {
|
|
@@ -447,8 +448,19 @@ async function startTextSearch(query: string, invocation: number) {
|
|
| 447 |
let pageContentsRead = Promise.resolve();
|
| 448 |
let textSearchFailed = false;
|
| 449 |
|
| 450 |
-
|
| 451 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 452 |
|
| 453 |
if (getSettings().enableImageSearch) {
|
| 454 |
updateImageSearchState("running");
|
|
@@ -466,7 +478,9 @@ async function startTextSearch(query: string, invocation: number) {
|
|
| 466 |
if (textResults.length === 0) {
|
| 467 |
const queryKeywords = await getKeywords(query, 10);
|
| 468 |
const keywordOutcome = await searchText(
|
| 469 |
-
queryKeywords.join(" "),
|
|
|
|
|
|
|
| 470 |
getSettings().searchResultsLimit,
|
| 471 |
);
|
| 472 |
textResults = keywordOutcome.results;
|
|
|
|
| 1 |
+
import { normalizeRelativeDates } from "@shared/relativeDates";
|
| 2 |
import gptTokenizer from "gpt-tokenizer";
|
| 3 |
import prettyMilliseconds from "pretty-ms";
|
| 4 |
import {
|
|
|
|
| 448 |
let pageContentsRead = Promise.resolve();
|
| 449 |
let textSearchFailed = false;
|
| 450 |
|
| 451 |
+
// "ๆๅนด" or "next month" name no year to the search engine, which then
|
| 452 |
+
// matches undated pages and leaves the answer to guess. Only this machine's
|
| 453 |
+
// clock knows which year the user means, so the query sent to the engine
|
| 454 |
+
// carries the anchor; the question, the history, and the prompt keep the
|
| 455 |
+
// wording the user typed.
|
| 456 |
+
const asOf = new Date();
|
| 457 |
+
|
| 458 |
+
const searchQuery = normalizeRelativeDates(
|
| 459 |
+
query.length > 2000 ? (await getKeywords(query, 20)).join(" ") : query,
|
| 460 |
+
asOf,
|
| 461 |
+
// The server rejects a longer query outright, so the anchor has to fit.
|
| 462 |
+
{ maxLength: 2000 },
|
| 463 |
+
).searchQuery;
|
| 464 |
|
| 465 |
if (getSettings().enableImageSearch) {
|
| 466 |
updateImageSearchState("running");
|
|
|
|
| 478 |
if (textResults.length === 0) {
|
| 479 |
const queryKeywords = await getKeywords(query, 10);
|
| 480 |
const keywordOutcome = await searchText(
|
| 481 |
+
normalizeRelativeDates(queryKeywords.join(" "), asOf, {
|
| 482 |
+
maxLength: 2000,
|
| 483 |
+
}).searchQuery,
|
| 484 |
getSettings().searchResultsLimit,
|
| 485 |
);
|
| 486 |
textResults = keywordOutcome.results;
|
docs/configuration.md
CHANGED
|
@@ -108,6 +108,14 @@ Each provider has its own terms, and each operator is responsible for following
|
|
| 108 |
|
| 109 |
That is why a provider that answers HTTP 429 is paused: the cascade skips it, without a network call, until its `Retry-After` has passed (60 seconds when the header is missing or invalid, and never more than an hour or less than a second). A public instance should set a key for each provider.
|
| 110 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 111 |
## Application Settings
|
| 112 |
|
| 113 |
Settings are stored in browser localStorage and can be changed via the Settings UI.
|
|
|
|
| 108 |
|
| 109 |
That is why a provider that answers HTTP 429 is paused: the cascade skips it, without a network call, until its `Retry-After` has passed (60 seconds when the header is missing or invalid, and never more than an hour or less than a second). A public instance should set a key for each provider.
|
| 110 |
|
| 111 |
+
### Official Page Lane
|
| 112 |
+
|
| 113 |
+
A search that asks for an official page can miss the official site: an engine answering "disney ไธไธชๆๆ็" with aggregators and fan blogs leaves the schedule page out of the set entirely, and no ranking of that set can recover it. With the lane on, when the query carries navigation intent and one of the returned results names a site, the server asks SearXNG a second, `site:`-scoped question and merges the answers before ranking. `docs/reranking.md` records the rule; this variable governs it.
|
| 114 |
+
|
| 115 |
+
| Variable | Default | Description |
|
| 116 |
+
| ------------------------------ | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
| 117 |
+
| `SEARCH_OFFICIAL_LANE_ENABLED` | `false` | Run the second, `site:`-scoped search when the query asks for an official page. Only `true` or `1` turns it on, and changing it takes a restart, since a process keeps the environment it was started with. It is off by default because it sends the engines a second request, which an instance they already rate-limit cannot afford. The lane runs only when SearXNG served the search (never after the fallback), never uses the last five seconds of the search deadline, and is canceled at eight seconds. It is one request with no retry, and its failures count toward neither the SearXNG circuit nor the engine health in `/status`: a failure only logs and is counted under `searches.officialLane`, never turning a served search into a 502. SearXNG still sees the extra request, so an engine that suspends the instance after a CAPTCHA does so for every search |
|
| 118 |
+
|
| 119 |
## Application Settings
|
| 120 |
|
| 121 |
Settings are stored in browser localStorage and can be changed via the Settings UI.
|
docs/overview.md
CHANGED
|
@@ -89,13 +89,14 @@ The system executes two parallel flows when a user submits a query:
|
|
| 89 |
### Search Flow
|
| 90 |
|
| 91 |
1. User submits a query via SearchForm
|
| 92 |
-
2.
|
| 93 |
-
3.
|
| 94 |
-
4.
|
| 95 |
-
5.
|
| 96 |
-
6.
|
| 97 |
-
7.
|
| 98 |
-
8.
|
|
|
|
| 99 |
|
| 100 |
### AI Generation Flow
|
| 101 |
|
|
|
|
| 89 |
### Search Flow
|
| 90 |
|
| 91 |
1. User submits a query via SearchForm
|
| 92 |
+
2. Relative month and year words (English and Chinese for now) are anchored to absolute ones before the request (`shared/relativeDates.ts`), so "next month" reaches the engines as "December 2026" and a relative phrase cannot leave the index without a year to match; the query as typed still drives the prompt, the history entry, and the UI
|
| 93 |
+
3. Client checks IndexedDB cache for matching query hash
|
| 94 |
+
4. On cache miss: authenticated HTTP request to `/search/text` or `/search/images`
|
| 95 |
+
5. Server verifies request token via `searchToken.ts` (CSRF protection)
|
| 96 |
+
6. `webSearchService.ts` forwards query to SearXNG at `http://127.0.0.1:8888`
|
| 97 |
+
7. Raw results are deduplicated, cleaned, and optionally reranked; a result from the site the query names gets a small score boost (`server/authorityRanking.ts`, see `docs/reranking.md`)
|
| 98 |
+
8. Image results keep the thumbnail URLs SearXNG returned, and results that came back without one are dropped; the client loads each tile on its own from `/thumbnail`, so the response does not wait on any thumbnail host
|
| 99 |
+
9. Results returned as structured JSON and cached in IndexedDB (15-minute freshness TTL)
|
| 100 |
|
| 101 |
### AI Generation Flow
|
| 102 |
|
docs/page-content.md
CHANGED
|
@@ -72,19 +72,34 @@ read degrades one result at a time rather than the whole answer.
|
|
| 72 |
## Extraction
|
| 73 |
|
| 74 |
`server/pageContentService.ts` converts HTML with `html-to-text`, preferring the
|
| 75 |
-
innermost `article` / `main` / `[role="main"]` container and skipping
|
| 76 |
-
header, footer, aside
|
|
|
|
| 77 |
via `@mozilla/readability` would score better on hostile layouts, but it needs a
|
| 78 |
full DOM implementation at runtime; `html-to-text` is already a dependency and
|
| 79 |
the passage ranking absorbs most of what boilerplate removal would have.
|
| 80 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 81 |
Encoding comes from the `Content-Type` header, falling back to the `<meta
|
| 82 |
charset>` the document declares, so a page that is not UTF-8 does not reach the
|
| 83 |
model as mojibake.
|
| 84 |
|
| 85 |
Passages are ranked by how much of the query they cover, with a prior for lead
|
| 86 |
passages (the definition or summary usually opens a page). The prior is worth at
|
| 87 |
-
most half a matched term, so it settles ties without outweighing coverage.
|
|
|
|
|
|
|
| 88 |
|
| 89 |
For pools of at most 256 passages, the server also scores every passage with
|
| 90 |
its local multilingual bi-encoder. Lexical and dense ranks are combined with
|
|
|
|
| 72 |
## Extraction
|
| 73 |
|
| 74 |
`server/pageContentService.ts` converts HTML with `html-to-text`, preferring the
|
| 75 |
+
innermost `article` / `main` / `[role="main"]` container and skipping inline
|
| 76 |
+
widgets; nav, header, footer, aside and forms are dropped by the scan described
|
| 77 |
+
below. Readability-style extraction
|
| 78 |
via `@mozilla/readability` would score better on hostile layouts, but it needs a
|
| 79 |
full DOM implementation at runtime; `html-to-text` is already a dependency and
|
| 80 |
the passage ranking absorbs most of what boilerplate removal would have.
|
| 81 |
|
| 82 |
+
Sites built out of plain `div`s put the same chrome where no selector names it,
|
| 83 |
+
so a single forward scan (`stripLinkHeavyBlocks`) runs before the conversion. It
|
| 84 |
+
drops `nav`, `aside`, `header`, `footer` and `form` blocks, and any block of 120
|
| 85 |
+
characters or more whose text is 60% or more link text, judged innermost-first
|
| 86 |
+
so that a wrapper which also holds the article survives. Inside `article`,
|
| 87 |
+
`main` or `role="main"`, link density drops nothing: a list of linked titles
|
| 88 |
+
there is usually the answer. The scan stays linear in the page size, which
|
| 89 |
+
matters because it runs on the main thread for any public URL: an unclosed
|
| 90 |
+
`<script>` runs to the end of the page, as a parser reads it, and a closing tag
|
| 91 |
+
with no open block to match skips the walk down the stack. A page that reads as
|
| 92 |
+
empty after the scan falls back to its unstripped text.
|
| 93 |
+
|
| 94 |
Encoding comes from the `Content-Type` header, falling back to the `<meta
|
| 95 |
charset>` the document declares, so a page that is not UTF-8 does not reach the
|
| 96 |
model as mojibake.
|
| 97 |
|
| 98 |
Passages are ranked by how much of the query they cover, with a prior for lead
|
| 99 |
passages (the definition or summary usually opens a page). The prior is worth at
|
| 100 |
+
most half a matched term, so it settles ties without outweighing coverage. A
|
| 101 |
+
second prior, worth 0.15, rewards a passage for repeating the page's own
|
| 102 |
+
`<title>` and `h1`/`h2` words, which a rail of unrelated teasers does not do.
|
| 103 |
|
| 104 |
For pools of at most 256 passages, the server also scores every passage with
|
| 105 |
its local multilingual bi-encoder. Lexical and dense ranks are combined with
|
docs/reranking.md
CHANGED
|
@@ -97,16 +97,57 @@ When `preserveTopResults = true`, the ranking algorithm:
|
|
| 97 |
|
| 98 |
This mode ensures the original top result is never displaced by the reranker, while still improving the ordering of subsequent results.
|
| 99 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 100 |
## Integration with Search Pipeline
|
| 101 |
|
| 102 |
The search endpoint coordinates reranking in `searchEndpointServerHook.ts`:
|
| 103 |
|
| 104 |
```
|
| 105 |
1. fetchSearXNG(query, searchType, limit) - internally fetches from SearXNG, deduplicates, and cleans results
|
| 106 |
-
2.
|
|
|
|
| 107 |
- If healthy: rankSearchResults(query, searchType, results, preserveTopResults)
|
| 108 |
- If unhealthy: return unranked results (fallback)
|
| 109 |
-
|
| 110 |
```
|
| 111 |
|
| 112 |
Reranking is applied to both text and image search results. For image results, titles and URLs are reformatted into the same text-tuple shape used for text search before being sent through the reranker, then the original image data (including thumbnails) is re-matched to the reranked order by URL. Text search passes `preserveTopResults: true` to keep the original top SearXNG result pinned, while image search does not.
|
|
|
|
| 97 |
|
| 98 |
This mode ensures the original top result is never displaced by the reranker, while still improving the ordering of subsequent results.
|
| 99 |
|
| 100 |
+
### Domain Priority
|
| 101 |
+
|
| 102 |
+
A page from the site the query names gets a small bonus on its semantic score before the filter runs (`server/authorityRanking.ts`, applied in `server/rankSearchResults.ts`). The reranker never sees the URL, so a query like "disney plus next month" ranks a blog roundup and disneyplus.com on their prose alone; the bonus lets the named site overtake a page whose snippet merely talks about it.
|
| 103 |
+
|
| 104 |
+
The bonus is added, not multiplied, because the reranker returns a raw cross-encoder logit: most documents score below zero, and a factor above one would push a negative score further down and demote the very site it means to promote.
|
| 105 |
+
|
| 106 |
+
The rule is narrow enough to explain in a sentence:
|
| 107 |
+
|
| 108 |
+
| Situation | Bonus | Example |
|
| 109 |
+
| ------------------------------------------- | ----- | -------------------------------------- |
|
| 110 |
+
| A query word equals the domain's label | +0.5 | `disney` โ disney.com, `wwe` โ wwe.com |
|
| 111 |
+
| The label is the query's words joined | +0.75 | `disney plus` โ disneyplus.com |
|
| 112 |
+
| The label is the query's words with hyphens | +0.75 | `coca cola` โ coca-cola.com |
|
| 113 |
+
| The label merely contains a query word | 0 | `disney` โ disneyfan.com |
|
| 114 |
+
|
| 115 |
+
Subdomains share their registrable domain's label, so investor.disney.com and www.disney.com both answer "disney". The label comes from the public suffix list, private section included (`tldts`), so bbc.co.uk answers "bbc", ministry.go.jp answers "ministry", and a tenant of a shared host such as someone.github.io answers its own name instead of "github". "+" is read as " plus ", because a hostname cannot contain it and a user typing "disney+" means the same brand.
|
| 116 |
+
|
| 117 |
+
Three guards keep the nudge from promoting the wrong page:
|
| 118 |
+
|
| 119 |
+
- Filler words never match on their own, so "plus size guide" does not promote plus.com and "news about disney" does not promote news.com. A label split into the query's words counts only when one part is four characters or more, unless a hyphen marks the split.
|
| 120 |
+
- The joined-label bonus needs the words the query is actually about: a label that spells only the start of the query leaves the remaining words unused, so "tesla investor relations" does not promote teslainvestor.com, and a label that starts with a filler word is skipped, so "the disney store" does not promote thedisney.com.
|
| 121 |
+
- When the query names a year, a page that states a different year is left alone: a brand's recap of last year's event is not the page the query meant, and fresher reporting should keep the top spot. A page that states no year at all keeps the bonus, because relative-time queries are anchored to a year before they reach the search ("next month December 2026") and the page that answers them often writes only "December".
|
| 122 |
+
|
| 123 |
+
The filler-word list is English. In another language, the other words of the query count as leftover, so the joined-label bonus rarely applies there, while the single-word bonus still does.
|
| 124 |
+
|
| 125 |
+
The bonus is applied before the standard-deviation filter, so it can also rescue a page the filter would have dropped. It cannot add a result the search did not return; that is a retrieval problem, not a ranking one. The pinned top result of a search is never boosted, so the score distribution the client reads relevance tags from keeps its shape.
|
| 126 |
+
|
| 127 |
+
### Official Page Lane
|
| 128 |
+
|
| 129 |
+
A bonus can reorder what a search returned, but it cannot summon a page the search never returned. When a query asks for an official page and the engine's results contain only aggregators and fan pages, there is nothing to promote, so the endpoint asks for the page directly: a second SearXNG query scoped to `site:<domain>`, whose results are merged into the set before ranking (`server/officialLane.ts`, wired in `server/searchEndpointServerHook.ts`).
|
| 130 |
+
|
| 131 |
+
The lane is off by default, because it sends the engines a second request; `SEARCH_OFFICIAL_LANE_ENABLED=true` turns it on. When it is on, two conditions have to hold, because a scoped second query costs latency and must not guess:
|
| 132 |
+
|
| 133 |
+
- The query carries navigation intent. Chinese has no word boundaries to split on, so the phrases are matched in the raw query (`ๅฎ็ฝ`, `ๅฎๆน็ฝ็ซ`, `ๅฎๆน็ซ`, `ๅฎๆน็ฝ้กต`, `ๅฎๆน้กต้ข`, `็ๅ`, `ๆ็`, `ๅบๆฌก`, `็ฅจไปท`, `่ฅไธๆถ้ด`, `ไธๆ ๆถ้ด`, `่ดญ็ฅจ`, `่ฎข็ฅจ`); English matches whole words (`official`, `hours`, `opening`, `schedule`, `showtime`, `showtimes`, `timetable`, `ticket`, `tickets`, `pricing`, `prices`). Other languages have no intent words yet, so their queries get no lane. The site has to appear in the query in the letters of its domain: "่ฟชๅฃซๅฐผ ๅฎ็ฝ" does not reach disney.com, while "disney ๅฎ็ฝ" does. Words that name the intent do not count as leftover for the domain match, so "disney plus showtimes" reaches disneyplus.com.
|
| 134 |
+
- One of the returned results already names the site, by the same rule the bonus uses (`namedDomain`). The domain is evidence from the search itself; no domain is derived from the query alone, since probing `<token>.com` would turn a search box into a scanner. A query that names a brand whose official site did not appear anywhere in the results gets no lane. The rule matches names, not ownership: for "disney world ticket prices", a third-party disneyworld.eu answers "disney world", while Disney's own disneyworld.disney.go.com answers "go", so the lane scopes the third-party site.
|
| 135 |
+
|
| 136 |
+
The lane adds at most three results, deduplicated by URL and kept to the registrable domain it scoped to, so an engine that ignores `site:` cannot smuggle an unrelated host in. They are placed behind the pinned first result: the ranking keeps the top result where it is and re-sorts only what follows, so a page appended past that window would never be re-sorted at all.
|
| 137 |
+
|
| 138 |
+
Every failure is the search it already has. The lane runs only when SearXNG served the search, since results from the fallback mean SearXNG has just failed. It never uses the last five seconds of the 25-second search deadline, and its request is canceled at eight seconds. The request goes through `fetchSupplementarySearXNG` (`server/webSearchService.ts`): one request with no retry, which counts toward neither the circuit breaker nor the engine-health counters, so a `site:` query that comes back empty cannot slow down, open the circuit for, or mark degraded the searches users are waiting on. SearXNG itself still sees the request, so an engine that suspends the instance after a CAPTCHA does so for every search. A failure or a timeout only logs and increments a counter: it can add results, never turn a served search into a 502. `/status` reports `searches.officialLane` with how often the lane ran, how many results it added, and how often it failed.
|
| 139 |
+
|
| 140 |
## Integration with Search Pipeline
|
| 141 |
|
| 142 |
The search endpoint coordinates reranking in `searchEndpointServerHook.ts`:
|
| 143 |
|
| 144 |
```
|
| 145 |
1. fetchSearXNG(query, searchType, limit) - internally fetches from SearXNG, deduplicates, and cleans results
|
| 146 |
+
2. Text search only, when `SEARCH_OFFICIAL_LANE_ENABLED` is on: the official page lane may merge results from a `site:`-scoped second query (see above)
|
| 147 |
+
3. Check getRerankerStatus()
|
| 148 |
- If healthy: rankSearchResults(query, searchType, results, preserveTopResults)
|
| 149 |
- If unhealthy: return unranked results (fallback)
|
| 150 |
+
4. Return structured JSON response
|
| 151 |
```
|
| 152 |
|
| 153 |
Reranking is applied to both text and image search results. For image results, titles and URLs are reformatted into the same text-tuple shape used for text search before being sent through the reranker, then the original image data (including thumbnails) is re-matched to the reranked order by URL. Text search passes `preserveTopResults: true` to keep the original top SearXNG result pinned, while image search does not.
|
package-lock.json
CHANGED
|
@@ -56,6 +56,7 @@
|
|
| 56 |
"shiki": "^4.0.0",
|
| 57 |
"temp-dir": "^3.0.0",
|
| 58 |
"throttleit": "^3.0.0",
|
|
|
|
| 59 |
"vite": "^8.0.0",
|
| 60 |
"wouter": "^3.3.0",
|
| 61 |
"yet-another-react-lightbox": "^3.21.6",
|
|
@@ -95,9 +96,9 @@
|
|
| 95 |
"license": "MIT"
|
| 96 |
},
|
| 97 |
"node_modules/@ai-sdk/gateway": {
|
| 98 |
-
"version": "4.0.
|
| 99 |
-
"resolved": "https://registry.npmjs.org/@ai-sdk/gateway/-/gateway-4.0.
|
| 100 |
-
"integrity": "sha512-
|
| 101 |
"license": "Apache-2.0",
|
| 102 |
"dependencies": {
|
| 103 |
"@ai-sdk/provider": "4.0.35",
|
|
@@ -2149,29 +2150,29 @@
|
|
| 2149 |
}
|
| 2150 |
},
|
| 2151 |
"node_modules/@shikijs/core": {
|
| 2152 |
-
"version": "4.
|
| 2153 |
-
"resolved": "https://registry.npmjs.org/@shikijs/core/-/core-4.
|
| 2154 |
-
"integrity": "sha512-
|
| 2155 |
"license": "MIT",
|
| 2156 |
"dependencies": {
|
| 2157 |
-
"@shikijs/primitive": "4.
|
| 2158 |
-
"@shikijs/types": "4.
|
| 2159 |
-
"@shikijs/vscode-textmate": "^10.
|
| 2160 |
"@types/hast": "^3.0.5",
|
| 2161 |
-
"hast-util-to-html": "^9.0.
|
| 2162 |
},
|
| 2163 |
"engines": {
|
| 2164 |
"node": ">=20"
|
| 2165 |
}
|
| 2166 |
},
|
| 2167 |
"node_modules/@shikijs/engine-javascript": {
|
| 2168 |
-
"version": "4.
|
| 2169 |
-
"resolved": "https://registry.npmjs.org/@shikijs/engine-javascript/-/engine-javascript-4.
|
| 2170 |
-
"integrity": "sha512-
|
| 2171 |
"license": "MIT",
|
| 2172 |
"dependencies": {
|
| 2173 |
-
"@shikijs/types": "4.
|
| 2174 |
-
"@shikijs/vscode-textmate": "^10.
|
| 2175 |
"oniguruma-to-es": "^4.3.6"
|
| 2176 |
},
|
| 2177 |
"engines": {
|
|
@@ -2179,38 +2180,38 @@
|
|
| 2179 |
}
|
| 2180 |
},
|
| 2181 |
"node_modules/@shikijs/engine-oniguruma": {
|
| 2182 |
-
"version": "4.
|
| 2183 |
-
"resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-4.
|
| 2184 |
-
"integrity": "sha512-
|
| 2185 |
"license": "MIT",
|
| 2186 |
"dependencies": {
|
| 2187 |
-
"@shikijs/types": "4.
|
| 2188 |
-
"@shikijs/vscode-textmate": "^10.
|
| 2189 |
},
|
| 2190 |
"engines": {
|
| 2191 |
"node": ">=20"
|
| 2192 |
}
|
| 2193 |
},
|
| 2194 |
"node_modules/@shikijs/langs": {
|
| 2195 |
-
"version": "4.
|
| 2196 |
-
"resolved": "https://registry.npmjs.org/@shikijs/langs/-/langs-4.
|
| 2197 |
-
"integrity": "sha512-
|
| 2198 |
"license": "MIT",
|
| 2199 |
"dependencies": {
|
| 2200 |
-
"@shikijs/types": "4.
|
| 2201 |
},
|
| 2202 |
"engines": {
|
| 2203 |
"node": ">=20"
|
| 2204 |
}
|
| 2205 |
},
|
| 2206 |
"node_modules/@shikijs/primitive": {
|
| 2207 |
-
"version": "4.
|
| 2208 |
-
"resolved": "https://registry.npmjs.org/@shikijs/primitive/-/primitive-4.
|
| 2209 |
-
"integrity": "sha512-
|
| 2210 |
"license": "MIT",
|
| 2211 |
"dependencies": {
|
| 2212 |
-
"@shikijs/types": "4.
|
| 2213 |
-
"@shikijs/vscode-textmate": "^10.
|
| 2214 |
"@types/hast": "^3.0.5"
|
| 2215 |
},
|
| 2216 |
"engines": {
|
|
@@ -2218,24 +2219,24 @@
|
|
| 2218 |
}
|
| 2219 |
},
|
| 2220 |
"node_modules/@shikijs/themes": {
|
| 2221 |
-
"version": "4.
|
| 2222 |
-
"resolved": "https://registry.npmjs.org/@shikijs/themes/-/themes-4.
|
| 2223 |
-
"integrity": "sha512-
|
| 2224 |
"license": "MIT",
|
| 2225 |
"dependencies": {
|
| 2226 |
-
"@shikijs/types": "4.
|
| 2227 |
},
|
| 2228 |
"engines": {
|
| 2229 |
"node": ">=20"
|
| 2230 |
}
|
| 2231 |
},
|
| 2232 |
"node_modules/@shikijs/types": {
|
| 2233 |
-
"version": "4.
|
| 2234 |
-
"resolved": "https://registry.npmjs.org/@shikijs/types/-/types-4.
|
| 2235 |
-
"integrity": "sha512-
|
| 2236 |
"license": "MIT",
|
| 2237 |
"dependencies": {
|
| 2238 |
-
"@shikijs/vscode-textmate": "^10.
|
| 2239 |
"@types/hast": "^3.0.5"
|
| 2240 |
},
|
| 2241 |
"engines": {
|
|
@@ -2243,9 +2244,9 @@
|
|
| 2243 |
}
|
| 2244 |
},
|
| 2245 |
"node_modules/@shikijs/vscode-textmate": {
|
| 2246 |
-
"version": "10.
|
| 2247 |
-
"resolved": "https://registry.npmjs.org/@shikijs/vscode-textmate/-/vscode-textmate-10.0.
|
| 2248 |
-
"integrity": "sha512-
|
| 2249 |
"license": "MIT"
|
| 2250 |
},
|
| 2251 |
"node_modules/@sindresorhus/is": {
|
|
@@ -2884,9 +2885,9 @@
|
|
| 2884 |
}
|
| 2885 |
},
|
| 2886 |
"node_modules/@vitejs/plugin-react": {
|
| 2887 |
-
"version": "6.
|
| 2888 |
-
"resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-6.
|
| 2889 |
-
"integrity": "sha512-
|
| 2890 |
"license": "MIT",
|
| 2891 |
"dependencies": {
|
| 2892 |
"@rolldown/pluginutils": "^1.0.1"
|
|
@@ -2895,9 +2896,9 @@
|
|
| 2895 |
"node": "^20.19.0 || >=22.12.0"
|
| 2896 |
},
|
| 2897 |
"peerDependencies": {
|
| 2898 |
-
"@rolldown/plugin-babel": "^0.1.7 || ^0.2.0",
|
| 2899 |
"babel-plugin-react-compiler": "^1.0.0",
|
| 2900 |
-
"oxc-transform-react": "
|
| 2901 |
"vite": "^8.0.0"
|
| 2902 |
},
|
| 2903 |
"peerDependenciesMeta": {
|
|
@@ -3024,12 +3025,12 @@
|
|
| 3024 |
}
|
| 3025 |
},
|
| 3026 |
"node_modules/ai": {
|
| 3027 |
-
"version": "7.0.
|
| 3028 |
-
"resolved": "https://registry.npmjs.org/ai/-/ai-7.0.
|
| 3029 |
-
"integrity": "sha512-
|
| 3030 |
"license": "Apache-2.0",
|
| 3031 |
"dependencies": {
|
| 3032 |
-
"@ai-sdk/gateway": "4.0.
|
| 3033 |
"@ai-sdk/provider": "4.0.35",
|
| 3034 |
"@ai-sdk/provider-utils": "5.0.72"
|
| 3035 |
},
|
|
@@ -3970,9 +3971,9 @@
|
|
| 3970 |
}
|
| 3971 |
},
|
| 3972 |
"node_modules/globals": {
|
| 3973 |
-
"version": "17.
|
| 3974 |
-
"resolved": "https://registry.npmjs.org/globals/-/globals-17.
|
| 3975 |
-
"integrity": "sha512-
|
| 3976 |
"dev": true,
|
| 3977 |
"license": "MIT",
|
| 3978 |
"engines": {
|
|
@@ -4062,9 +4063,9 @@
|
|
| 4062 |
}
|
| 4063 |
},
|
| 4064 |
"node_modules/hast-util-to-html": {
|
| 4065 |
-
"version": "9.0.
|
| 4066 |
-
"resolved": "https://registry.npmjs.org/hast-util-to-html/-/hast-util-to-html-9.0.
|
| 4067 |
-
"integrity": "sha512-
|
| 4068 |
"license": "MIT",
|
| 4069 |
"dependencies": {
|
| 4070 |
"@types/hast": "^3.0.0",
|
|
@@ -7213,18 +7214,18 @@
|
|
| 7213 |
}
|
| 7214 |
},
|
| 7215 |
"node_modules/shiki": {
|
| 7216 |
-
"version": "4.
|
| 7217 |
-
"resolved": "https://registry.npmjs.org/shiki/-/shiki-4.
|
| 7218 |
-
"integrity": "sha512-
|
| 7219 |
"license": "MIT",
|
| 7220 |
"dependencies": {
|
| 7221 |
-
"@shikijs/core": "4.
|
| 7222 |
-
"@shikijs/engine-javascript": "4.
|
| 7223 |
-
"@shikijs/engine-oniguruma": "4.
|
| 7224 |
-
"@shikijs/langs": "4.
|
| 7225 |
-
"@shikijs/themes": "4.
|
| 7226 |
-
"@shikijs/types": "4.
|
| 7227 |
-
"@shikijs/vscode-textmate": "^10.
|
| 7228 |
"@types/hast": "^3.0.5"
|
| 7229 |
},
|
| 7230 |
"engines": {
|
|
@@ -7520,7 +7521,6 @@
|
|
| 7520 |
"version": "7.0.25",
|
| 7521 |
"resolved": "https://registry.npmjs.org/tldts/-/tldts-7.0.25.tgz",
|
| 7522 |
"integrity": "sha512-keinCnPbwXEUG3ilrWQZU+CqcTTzHq9m2HhoUP2l7Xmi8l1LuijAXLpAJ5zRW+ifKTNscs4NdCkfkDCBYm352w==",
|
| 7523 |
-
"dev": true,
|
| 7524 |
"license": "MIT",
|
| 7525 |
"dependencies": {
|
| 7526 |
"tldts-core": "^7.0.25"
|
|
@@ -7533,7 +7533,6 @@
|
|
| 7533 |
"version": "7.0.25",
|
| 7534 |
"resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-7.0.25.tgz",
|
| 7535 |
"integrity": "sha512-ZjCZK0rppSBu7rjHYDYsEaMOIbbT+nWF57hKkv4IUmZWBNrBWBOjIElc0mKRgLM8bm7x/BBlof6t2gi/Oq/Asw==",
|
| 7536 |
-
"dev": true,
|
| 7537 |
"license": "MIT"
|
| 7538 |
},
|
| 7539 |
"node_modules/tough-cookie": {
|
|
|
|
| 56 |
"shiki": "^4.0.0",
|
| 57 |
"temp-dir": "^3.0.0",
|
| 58 |
"throttleit": "^3.0.0",
|
| 59 |
+
"tldts": "^7.0.25",
|
| 60 |
"vite": "^8.0.0",
|
| 61 |
"wouter": "^3.3.0",
|
| 62 |
"yet-another-react-lightbox": "^3.21.6",
|
|
|
|
| 96 |
"license": "MIT"
|
| 97 |
},
|
| 98 |
"node_modules/@ai-sdk/gateway": {
|
| 99 |
+
"version": "4.0.133",
|
| 100 |
+
"resolved": "https://registry.npmjs.org/@ai-sdk/gateway/-/gateway-4.0.133.tgz",
|
| 101 |
+
"integrity": "sha512-6DmtrnwvTZG6RFhZscsnUX0XKxA+o2Y/EkZUGWWqFITez/gvOGG/PRKITjwPupUci5vbORThwqkbCj1/066Reg==",
|
| 102 |
"license": "Apache-2.0",
|
| 103 |
"dependencies": {
|
| 104 |
"@ai-sdk/provider": "4.0.35",
|
|
|
|
| 2150 |
}
|
| 2151 |
},
|
| 2152 |
"node_modules/@shikijs/core": {
|
| 2153 |
+
"version": "4.6.0",
|
| 2154 |
+
"resolved": "https://registry.npmjs.org/@shikijs/core/-/core-4.6.0.tgz",
|
| 2155 |
+
"integrity": "sha512-bfd4OKgvoyXYiYmMRuleA656P4V7M/Du9lcTl38Ngm6KszMj7bq4J1IGZwSYR4c95Tv5UQALtYNWUwaI9UT3xw==",
|
| 2156 |
"license": "MIT",
|
| 2157 |
"dependencies": {
|
| 2158 |
+
"@shikijs/primitive": "4.6.0",
|
| 2159 |
+
"@shikijs/types": "4.6.0",
|
| 2160 |
+
"@shikijs/vscode-textmate": "^10.1.0",
|
| 2161 |
"@types/hast": "^3.0.5",
|
| 2162 |
+
"hast-util-to-html": "^9.0.6"
|
| 2163 |
},
|
| 2164 |
"engines": {
|
| 2165 |
"node": ">=20"
|
| 2166 |
}
|
| 2167 |
},
|
| 2168 |
"node_modules/@shikijs/engine-javascript": {
|
| 2169 |
+
"version": "4.6.0",
|
| 2170 |
+
"resolved": "https://registry.npmjs.org/@shikijs/engine-javascript/-/engine-javascript-4.6.0.tgz",
|
| 2171 |
+
"integrity": "sha512-K0oqxFoL+kEMdz89mHLFO15Tsbv3kFZ6WR1cXZKMvLSc+oqaM00A6pyga1qSeXB22dP0tTiSApRcGBaKWsEccg==",
|
| 2172 |
"license": "MIT",
|
| 2173 |
"dependencies": {
|
| 2174 |
+
"@shikijs/types": "4.6.0",
|
| 2175 |
+
"@shikijs/vscode-textmate": "^10.1.0",
|
| 2176 |
"oniguruma-to-es": "^4.3.6"
|
| 2177 |
},
|
| 2178 |
"engines": {
|
|
|
|
| 2180 |
}
|
| 2181 |
},
|
| 2182 |
"node_modules/@shikijs/engine-oniguruma": {
|
| 2183 |
+
"version": "4.6.0",
|
| 2184 |
+
"resolved": "https://registry.npmjs.org/@shikijs/engine-oniguruma/-/engine-oniguruma-4.6.0.tgz",
|
| 2185 |
+
"integrity": "sha512-aggd6Q7SuJOXNnsUbfG2XesxBJKBMAw/PDTUo9PyRjMaaJJ2dNrI+q0C+25ARXAK9yLHAlwxc9oRT2u68YKZ7w==",
|
| 2186 |
"license": "MIT",
|
| 2187 |
"dependencies": {
|
| 2188 |
+
"@shikijs/types": "4.6.0",
|
| 2189 |
+
"@shikijs/vscode-textmate": "^10.1.0"
|
| 2190 |
},
|
| 2191 |
"engines": {
|
| 2192 |
"node": ">=20"
|
| 2193 |
}
|
| 2194 |
},
|
| 2195 |
"node_modules/@shikijs/langs": {
|
| 2196 |
+
"version": "4.6.0",
|
| 2197 |
+
"resolved": "https://registry.npmjs.org/@shikijs/langs/-/langs-4.6.0.tgz",
|
| 2198 |
+
"integrity": "sha512-1yMEX3oCVV54LUq/ovfpRkDQoYDkcvjSWFFa/YLUDMiSoRzMi2uxLJYbv41ahT8Lu9lFMaa7hecd3Q39trqUyg==",
|
| 2199 |
"license": "MIT",
|
| 2200 |
"dependencies": {
|
| 2201 |
+
"@shikijs/types": "4.6.0"
|
| 2202 |
},
|
| 2203 |
"engines": {
|
| 2204 |
"node": ">=20"
|
| 2205 |
}
|
| 2206 |
},
|
| 2207 |
"node_modules/@shikijs/primitive": {
|
| 2208 |
+
"version": "4.6.0",
|
| 2209 |
+
"resolved": "https://registry.npmjs.org/@shikijs/primitive/-/primitive-4.6.0.tgz",
|
| 2210 |
+
"integrity": "sha512-c54+TQEo9ToqgfL/AU8FpjnV09+GA9Oa7M5ROkkXteZfp5XzpAJGXjjvZQFQioJOwm/w/6Z3lIzQstAkl8coag==",
|
| 2211 |
"license": "MIT",
|
| 2212 |
"dependencies": {
|
| 2213 |
+
"@shikijs/types": "4.6.0",
|
| 2214 |
+
"@shikijs/vscode-textmate": "^10.1.0",
|
| 2215 |
"@types/hast": "^3.0.5"
|
| 2216 |
},
|
| 2217 |
"engines": {
|
|
|
|
| 2219 |
}
|
| 2220 |
},
|
| 2221 |
"node_modules/@shikijs/themes": {
|
| 2222 |
+
"version": "4.6.0",
|
| 2223 |
+
"resolved": "https://registry.npmjs.org/@shikijs/themes/-/themes-4.6.0.tgz",
|
| 2224 |
+
"integrity": "sha512-qHAVAs80RiGD+IRuH9G+JPDOEc9A/kHsng4EzCUCwB9bLzW6Z0y9WETh2YyCcADJBmfrIaJ1Jp7KBNP6FHkgtQ==",
|
| 2225 |
"license": "MIT",
|
| 2226 |
"dependencies": {
|
| 2227 |
+
"@shikijs/types": "4.6.0"
|
| 2228 |
},
|
| 2229 |
"engines": {
|
| 2230 |
"node": ">=20"
|
| 2231 |
}
|
| 2232 |
},
|
| 2233 |
"node_modules/@shikijs/types": {
|
| 2234 |
+
"version": "4.6.0",
|
| 2235 |
+
"resolved": "https://registry.npmjs.org/@shikijs/types/-/types-4.6.0.tgz",
|
| 2236 |
+
"integrity": "sha512-+ENG1adsKt/X7cRYw1Nl7BEpBmPOjmQ7bR/eGmHuMlT8gtOFf3KEi5l31DJYyAWhW6Y6bjP1plU7FmaxWGENFw==",
|
| 2237 |
"license": "MIT",
|
| 2238 |
"dependencies": {
|
| 2239 |
+
"@shikijs/vscode-textmate": "^10.1.0",
|
| 2240 |
"@types/hast": "^3.0.5"
|
| 2241 |
},
|
| 2242 |
"engines": {
|
|
|
|
| 2244 |
}
|
| 2245 |
},
|
| 2246 |
"node_modules/@shikijs/vscode-textmate": {
|
| 2247 |
+
"version": "10.1.0",
|
| 2248 |
+
"resolved": "https://registry.npmjs.org/@shikijs/vscode-textmate/-/vscode-textmate-10.1.0.tgz",
|
| 2249 |
+
"integrity": "sha512-cVBuhDQjBZaNMTwBquYfPydrUP+E3hE6oYGasx8h6ZrODIM7/r+gxLDXdd66oszbrggZHEntdE6j+5gYIVyMWQ==",
|
| 2250 |
"license": "MIT"
|
| 2251 |
},
|
| 2252 |
"node_modules/@sindresorhus/is": {
|
|
|
|
| 2885 |
}
|
| 2886 |
},
|
| 2887 |
"node_modules/@vitejs/plugin-react": {
|
| 2888 |
+
"version": "6.2.1",
|
| 2889 |
+
"resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-6.2.1.tgz",
|
| 2890 |
+
"integrity": "sha512-9KyWCij3r0+qLG3Q62vBmv0VyevmYibAtxaCIVK9o0k6CXgaajZRIe8tpfchQcdZArJ5b/7OrIz4W3b2uxVRDA==",
|
| 2891 |
"license": "MIT",
|
| 2892 |
"dependencies": {
|
| 2893 |
"@rolldown/pluginutils": "^1.0.1"
|
|
|
|
| 2896 |
"node": "^20.19.0 || >=22.12.0"
|
| 2897 |
},
|
| 2898 |
"peerDependencies": {
|
| 2899 |
+
"@rolldown/plugin-babel": "^0.1.7 || ^0.2.0 || ^0.3.0",
|
| 2900 |
"babel-plugin-react-compiler": "^1.0.0",
|
| 2901 |
+
"oxc-transform-react": ">=0.152.0 <0.157.0",
|
| 2902 |
"vite": "^8.0.0"
|
| 2903 |
},
|
| 2904 |
"peerDependenciesMeta": {
|
|
|
|
| 3025 |
}
|
| 3026 |
},
|
| 3027 |
"node_modules/ai": {
|
| 3028 |
+
"version": "7.0.160",
|
| 3029 |
+
"resolved": "https://registry.npmjs.org/ai/-/ai-7.0.160.tgz",
|
| 3030 |
+
"integrity": "sha512-h48o8i8NTDuHUZhiLDm9NHufEk/L3dkrJ8E9liXMcd4K4hAdI8bfamtbRJylyQeKQ599mnWNplTvyH/SbtpolQ==",
|
| 3031 |
"license": "Apache-2.0",
|
| 3032 |
"dependencies": {
|
| 3033 |
+
"@ai-sdk/gateway": "4.0.133",
|
| 3034 |
"@ai-sdk/provider": "4.0.35",
|
| 3035 |
"@ai-sdk/provider-utils": "5.0.72"
|
| 3036 |
},
|
|
|
|
| 3971 |
}
|
| 3972 |
},
|
| 3973 |
"node_modules/globals": {
|
| 3974 |
+
"version": "17.14.0",
|
| 3975 |
+
"resolved": "https://registry.npmjs.org/globals/-/globals-17.14.0.tgz",
|
| 3976 |
+
"integrity": "sha512-UtdkTVyAJePQv0J2M9Xe4L0ruNSDV314XiIJsADI2Zf40bcdAonE/D/OhAU8AT99oij9mhLByb+NvA3QWZYgCg==",
|
| 3977 |
"dev": true,
|
| 3978 |
"license": "MIT",
|
| 3979 |
"engines": {
|
|
|
|
| 4063 |
}
|
| 4064 |
},
|
| 4065 |
"node_modules/hast-util-to-html": {
|
| 4066 |
+
"version": "9.0.6",
|
| 4067 |
+
"resolved": "https://registry.npmjs.org/hast-util-to-html/-/hast-util-to-html-9.0.6.tgz",
|
| 4068 |
+
"integrity": "sha512-1SZUxCN3TtJAE7e2BQN2MBHsoZ1gnmi9GoCP5Di59CSWptIezSAW09yDZyVbanb78V9eCwrKqdQsjtfVTxYHWA==",
|
| 4069 |
"license": "MIT",
|
| 4070 |
"dependencies": {
|
| 4071 |
"@types/hast": "^3.0.0",
|
|
|
|
| 7214 |
}
|
| 7215 |
},
|
| 7216 |
"node_modules/shiki": {
|
| 7217 |
+
"version": "4.6.0",
|
| 7218 |
+
"resolved": "https://registry.npmjs.org/shiki/-/shiki-4.6.0.tgz",
|
| 7219 |
+
"integrity": "sha512-imjLsVHgPuWhvpU3P+zOekvMYqnel7QmKsFC4Sn0fCuAvaaalBsxwKEsrU8Ko5BdFnZA6Go/8a0RzahS634UgA==",
|
| 7220 |
"license": "MIT",
|
| 7221 |
"dependencies": {
|
| 7222 |
+
"@shikijs/core": "4.6.0",
|
| 7223 |
+
"@shikijs/engine-javascript": "4.6.0",
|
| 7224 |
+
"@shikijs/engine-oniguruma": "4.6.0",
|
| 7225 |
+
"@shikijs/langs": "4.6.0",
|
| 7226 |
+
"@shikijs/themes": "4.6.0",
|
| 7227 |
+
"@shikijs/types": "4.6.0",
|
| 7228 |
+
"@shikijs/vscode-textmate": "^10.1.0",
|
| 7229 |
"@types/hast": "^3.0.5"
|
| 7230 |
},
|
| 7231 |
"engines": {
|
|
|
|
| 7521 |
"version": "7.0.25",
|
| 7522 |
"resolved": "https://registry.npmjs.org/tldts/-/tldts-7.0.25.tgz",
|
| 7523 |
"integrity": "sha512-keinCnPbwXEUG3ilrWQZU+CqcTTzHq9m2HhoUP2l7Xmi8l1LuijAXLpAJ5zRW+ifKTNscs4NdCkfkDCBYm352w==",
|
|
|
|
| 7524 |
"license": "MIT",
|
| 7525 |
"dependencies": {
|
| 7526 |
"tldts-core": "^7.0.25"
|
|
|
|
| 7533 |
"version": "7.0.25",
|
| 7534 |
"resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-7.0.25.tgz",
|
| 7535 |
"integrity": "sha512-ZjCZK0rppSBu7rjHYDYsEaMOIbbT+nWF57hKkv4IUmZWBNrBWBOjIElc0mKRgLM8bm7x/BBlof6t2gi/Oq/Asw==",
|
|
|
|
| 7536 |
"license": "MIT"
|
| 7537 |
},
|
| 7538 |
"node_modules/tough-cookie": {
|
package.json
CHANGED
|
@@ -88,6 +88,7 @@
|
|
| 88 |
"shiki": "^4.0.0",
|
| 89 |
"temp-dir": "^3.0.0",
|
| 90 |
"throttleit": "^3.0.0",
|
|
|
|
| 91 |
"vite": "^8.0.0",
|
| 92 |
"wouter": "^3.3.0",
|
| 93 |
"yet-another-react-lightbox": "^3.21.6",
|
|
|
|
| 88 |
"shiki": "^4.0.0",
|
| 89 |
"temp-dir": "^3.0.0",
|
| 90 |
"throttleit": "^3.0.0",
|
| 91 |
+
"tldts": "^7.0.25",
|
| 92 |
"vite": "^8.0.0",
|
| 93 |
"wouter": "^3.3.0",
|
| 94 |
"yet-another-react-lightbox": "^3.21.6",
|