Adds an optional plugin that shows a generated summary above the search
results, similar to the answer boxes in Brave and Google. The text is
produced by an LLM server the administrator runs, reached over the
OpenAI chat completions API, so queries never leave the operator's own
network.
The summary is grounded on the top search results rather than the
model's training data. It is generated asynchronously: post_search adds
an empty placeholder and returns, and the browser fills it from the
/ai_summary endpoint, which streams the answer as NDJSON. No summary is
generated beyond page one, outside the general category, for non-HTML
formats, or when an engine already answered with an infobox or an
instant answer.
The client side follows the existing plugin pattern: one file in
client/simple/src/js/plugin/, one conditional load in router.ts, one
LESS import. No build configuration changes and no new dependencies.
The plugin is not activated by default. Instance defaults live in an
ai_summary: section; the server, model, API key and grounding are user
preferences, and all four can be locked.
Signed-off-by: Jason Witty <jasonpwitty+github@proton.me>
The results were previously HTML-escaped for some reason.
That doesn't really make much sense because they never got
unescaped anywhere.
For example, `A&B` gets escaped to `A&B` and never unescaped,
so the autocompletion frontend shows `A&B`, which isn't user-friendly
at all.
Also, it doesn't make sense to escape the full JSON instead
of only escaping the suggestion texts individually because that
makes it even more unclear.
The code used here has always been "bad" because `about` shouldn't be used
as data source, but the engine probably broke when type checks / dataclasses
for the about parameter in engines has been added with
<https://github.com/searxng/searxng/pull/6258>.
Error log:
```
WARNING searx.engines.public domain im: ErrorContext('searx/engines/public_domain_image_archive.py', 143, '\'url\': _clean_url(f"{about[\'website\']}/images/{result[\'objectID\']}"),', 'TypeError', None, ("'EngineAbout' object is not subscriptable",)) False
ERROR searx.engines.public domain im: exception : 'EngineAbout' object is not subscriptable
Traceback (most recent call last):
File "/home/bnyro/Projects/searxng/searx/search/processors/online.py", line 253, in search
search_results = self._search_basic(query, params)
File "/home/bnyro/Projects/searxng/searx/search/processors/online.py", line 239, in _search_basic
return self.engine.response(response)
~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^
File "/home/bnyro/Projects/searxng/searx/engines/public_domain_image_archive.py", line 143, in response
'url': _clean_url(f"{about['website']}/images/{result['objectID']}"),
~~~~~^^^^^^^^^^^
TypeError: 'EngineAbout' object is not subscriptable
```
Qwant now requires a `datadome` cookie that it returns
in the first search response as a `Set-Cookie`.
This cookie has to be sent for all requests, otherwise they
will be blocked.
This means that now, the first search request is blocked (results in CAPTCHA),
and only the subsequent searches work (same happens on the Qwant website for me).
However, I don't think it's worth repeating the same
search request multiple times very quickly because
that also makes us more suspicious.
Some search engines encode domains that contain special characters
in the IDN format, i.e. by using Punycode (https://en.wikipedia.org/wiki/Punycode).
This means that domains formatted like that are not human readable
and thus very unintuive.
Also, as only some engines use Punycode, this often causes a result
to appear multiple times, once with a Punycode domain (e.g. xn--allestrungen-9ib.de),
and once with a normal domain (e.g. allestörungen.de).
Always formatting the domains nicely has the caveat that the official sites
are harder to distinguish from malicious clones that just swapped out some
characters by using Punycode, e.g. replaced `a` with `á`. I don't think
that will be much of an issue though - I don't think Punycode results
previously stopped users from clicking the link, and I also think that
most search engines filter out such bad results or don't even have them
indexed.
Changes:
- the `embed.js` request now requires a user agent header
- we include a user agent in the "actual" request (I dropped it by accident)
- we only send the first 4 decimal places of the location
instead of 7+ (not required, but harder to detect)
Refactor engines that parse ISO 8601 dates with strptime to use
fromisoformat instead. In most cases this is a direct replacement of
strptime(text, "format") with fromisoformat(text).
For engines where the source has a trailing "Z" that strptime consumed
as a literal (e.g. "%Y-%m-%dT%H:%M:%S.%fZ" in huggingface.py), add
rstrip("Z") to keep the output naive and preserve the existing behavior.
In sogou.py the date is extracted with a regular expression, which can
yield strings like "2026-7-11". strptime accepts this via its format
string, but fromisoformat does not. To preserve the existing behavior
and satisfy the format fromisoformat expects, add zero-padding for the
month and day.
Closes: #6098
---------
Signed-off-by: OneVth <onebrotravel@gmail.com>
* [mod] show img_load_error.svg if thumbnail fails to load (#6368)
Brave and a few other engines occasionally provide dead URLs for the thumbnails,
which looks pretty ugly in the UI. As already with the image search, in case of
an error img_load_error.svg should be displayed here.
Signed-off-by: Markus Heiser <markus.heiser@darmarit.de>
* [build] /static (#6368)
---------
Signed-off-by: Markus Heiser <markus.heiser@darmarit.de>
`engines` only contains active engines. Since `google` was set to
inactive, it's no longer part of the , making the engine crash.
With this workaround, we directly load the engine traits from the data
dictionary instead of going the intermediate step with the Google engine.