From 054b3a5c0df00635878c5dbbba3e71c07836442b Mon Sep 17 00:00:00 2001 From: jasonwitty Date: Tue, 11 Aug 2026 12:17:09 -0700 Subject: [PATCH] [fix] simple theme: show why the AI summary failed The error box said only "Error loading the AI summary". The status of the failed request was raised and then dropped, so a user could not tell an LLM server that is unreachable (502) from a plugin that is switched off (403) or a request that was rejected (400). The reason is now appended to the message. On a phone or in a webview, where dev tools are not an option, it is the only thing the person looking at the failure has to go on. Co-Authored-By: Claude Opus 5 --- client/simple/src/js/plugin/AiSummary.ts | 9 +++++++-- .../simple/chunk/{DjTnIwxs.min.js => DBBLNmaq.min.js} | 6 +++--- searx/static/themes/simple/chunk/DBBLNmaq.min.js.map | 1 + searx/static/themes/simple/chunk/DjTnIwxs.min.js.map | 1 - searx/static/themes/simple/manifest.json | 2 +- searx/static/themes/simple/sxng-core.min.js | 4 ++-- 6 files changed, 14 insertions(+), 9 deletions(-) rename searx/static/themes/simple/chunk/{DjTnIwxs.min.js => DBBLNmaq.min.js} (74%) create mode 100644 searx/static/themes/simple/chunk/DBBLNmaq.min.js.map delete mode 100644 searx/static/themes/simple/chunk/DjTnIwxs.min.js.map diff --git a/client/simple/src/js/plugin/AiSummary.ts b/client/simple/src/js/plugin/AiSummary.ts index f12ad5618..c946b3149 100644 --- a/client/simple/src/js/plugin/AiSummary.ts +++ b/client/simple/src/js/plugin/AiSummary.ts @@ -157,7 +157,9 @@ export default class AiSummary extends Plugin { signal: controller.signal }); if (!res.ok) { - throw new Error(res.statusText); + // the status is shown to the user: it is often the only thing + // available to diagnose a failure on a device without dev tools + throw new Error(`HTTP ${res.status}`); } if (res.body) { @@ -195,8 +197,11 @@ export default class AiSummary extends Plugin { private static showError(box: HTMLElement, error: unknown): void { console.error("Error loading AI summary:", error); + const message = settings.translations?.error_loading_ai_summary ?? "Error loading the AI summary"; + const reason = error instanceof Error && error.message ? ` (${error.message})` : ""; + const errorElement = Object.assign(document.createElement("div"), { - textContent: settings.translations?.error_loading_ai_summary ?? "Error loading the AI summary", + textContent: `${message}${reason}`, className: "dialog-error" }); errorElement.setAttribute("role", "alert"); diff --git a/searx/static/themes/simple/chunk/DjTnIwxs.min.js b/searx/static/themes/simple/chunk/DBBLNmaq.min.js similarity index 74% rename from searx/static/themes/simple/chunk/DjTnIwxs.min.js rename to searx/static/themes/simple/chunk/DBBLNmaq.min.js index b519ae259..7cde36456 100644 --- a/searx/static/themes/simple/chunk/DjTnIwxs.min.js +++ b/searx/static/themes/simple/chunk/DBBLNmaq.min.js @@ -1,4 +1,4 @@ -import{a as e,i as t}from"../sxng-core.min.js";import{t as n}from"./DK4yUVpy.min.js";var r=5,i=12,a=300,o=1e3,s=class s extends e{messages=[];context=[];controller;constructor(){super(`ai_summary`)}async run(){let e=document.getElementById(`ai_summary`);if(e)try{let{query:t}=e.dataset;if(!t)return;e.dataset.grounding===`1`&&(this.context=s.collectContext()),this.wireControls(e),this.messages.push({role:`user`,content:t}),await this.exchange(e)}catch(t){s.showError(e,t)}}async post(){}static collectContext(){let e=[];for(let t of document.querySelectorAll(`#urls article.result`)){if(e.length>=r)break;let n=t.querySelector(`h3 a`);n&&e.push({title:(n.textContent??``).trim().slice(0,a),url:n.href,snippet:(t.querySelector(`.content`)?.textContent??``).trim().slice(0,o)})}return e}wireControls(e){let t=e.querySelector(`.ai-summary-body`),r=e.querySelector(`.ai-summary-more`),a=e.querySelector(`.ai-summary-followup`);n(t),n(r),n(a),r.addEventListener(`click`,()=>{let e=t.classList.toggle(`collapsed`);r.textContent=e?r.dataset.btnTextCollapsed??``:r.dataset.btnTextNotCollapsed??``,a.classList.toggle(`invisible`,e)}),a.addEventListener(`submit`,t=>{t.preventDefault();let r=a.querySelector(`input`);n(r);let o=r.value.trim();if(!o||this.controller)return;r.value=``,this.messages.push({role:`user`,content:o}),this.messages.splice(0,this.messages.length-(i-1));let s=Object.assign(document.createElement(`p`),{textContent:o,className:`ai-summary-question`});e.querySelector(`.ai-summary-answers`)?.append(s),this.exchange(e)})}async exchange(e){let t=e.querySelector(`.ai-summary-answers`);n(t);let r=Object.assign(document.createElement(`p`),{className:`ai-summary-content typing`});t.append(r);let i=new AbortController;this.controller=i;let a=``,o=t=>{if(!t.trim())return;let n=JSON.parse(t);if(n.error)throw Error(n.error);if(n.delta&&(a+=n.delta,r.textContent=a,this.updateMoreButton(e)),n.done&&n.model){let t=e.querySelector(`.ai-summary-model`);t&&(t.textContent=n.model)}};try{let e=await fetch(`./ai_summary`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({messages:this.messages,context:this.context}),signal:i.signal});if(!e.ok)throw Error(e.statusText);if(e.body){let t=e.body.getReader(),n=new TextDecoder,r=``;for(;;){let{done:e,value:i}=await t.read();if(e)break;r+=n.decode(i,{stream:!0});let a=r.split(` +import{a as e,i as t}from"../sxng-core.min.js";import{t as n}from"./DK4yUVpy.min.js";var r=5,i=12,a=300,o=1e3,s=class s extends e{messages=[];context=[];controller;constructor(){super(`ai_summary`)}async run(){let e=document.getElementById(`ai_summary`);if(e)try{let{query:t}=e.dataset;if(!t)return;e.dataset.grounding===`1`&&(this.context=s.collectContext()),this.wireControls(e),this.messages.push({role:`user`,content:t}),await this.exchange(e)}catch(t){s.showError(e,t)}}async post(){}static collectContext(){let e=[];for(let t of document.querySelectorAll(`#urls article.result`)){if(e.length>=r)break;let n=t.querySelector(`h3 a`);n&&e.push({title:(n.textContent??``).trim().slice(0,a),url:n.href,snippet:(t.querySelector(`.content`)?.textContent??``).trim().slice(0,o)})}return e}wireControls(e){let t=e.querySelector(`.ai-summary-body`),r=e.querySelector(`.ai-summary-more`),a=e.querySelector(`.ai-summary-followup`);n(t),n(r),n(a),r.addEventListener(`click`,()=>{let e=t.classList.toggle(`collapsed`);r.textContent=e?r.dataset.btnTextCollapsed??``:r.dataset.btnTextNotCollapsed??``,a.classList.toggle(`invisible`,e)}),a.addEventListener(`submit`,t=>{t.preventDefault();let r=a.querySelector(`input`);n(r);let o=r.value.trim();if(!o||this.controller)return;r.value=``,this.messages.push({role:`user`,content:o}),this.messages.splice(0,this.messages.length-(i-1));let s=Object.assign(document.createElement(`p`),{textContent:o,className:`ai-summary-question`});e.querySelector(`.ai-summary-answers`)?.append(s),this.exchange(e)})}async exchange(e){let t=e.querySelector(`.ai-summary-answers`);n(t);let r=Object.assign(document.createElement(`p`),{className:`ai-summary-content typing`});t.append(r);let i=new AbortController;this.controller=i;let a=``,o=t=>{if(!t.trim())return;let n=JSON.parse(t);if(n.error)throw Error(n.error);if(n.delta&&(a+=n.delta,r.textContent=a,this.updateMoreButton(e)),n.done&&n.model){let t=e.querySelector(`.ai-summary-model`);t&&(t.textContent=n.model)}};try{let e=await fetch(`./ai_summary`,{method:`POST`,headers:{"Content-Type":`application/json`},body:JSON.stringify({messages:this.messages,context:this.context}),signal:i.signal});if(!e.ok)throw Error(`HTTP ${e.status}`);if(e.body){let t=e.body.getReader(),n=new TextDecoder,r=``;for(;;){let{done:e,value:i}=await t.read();if(e)break;r+=n.decode(i,{stream:!0});let a=r.split(` `);r=a.pop()??``;for(let e of a)o(e)}o(r)}else{let t=await e.text();for(let e of t.split(` -`))o(e)}this.messages.push({role:`assistant`,content:a})}catch(t){s.showError(e,t)}finally{r.classList.remove(`typing`),this.controller=void 0,this.updateMoreButton(e)}}static showError(e,n){console.error(`Error loading AI summary:`,n);let r=Object.assign(document.createElement(`div`),{textContent:t.translations?.error_loading_ai_summary??`Error loading the AI summary`,className:`dialog-error`});r.setAttribute(`role`,`alert`),(e.querySelector(`.ai-summary-answers`)??e).append(r)}updateMoreButton(e){let t=e.querySelector(`.ai-summary-body`),n=e.querySelector(`.ai-summary-more`);t&&n&&(!t.classList.contains(`collapsed`)||t.scrollHeight>t.clientHeight+4)&&n.classList.remove(`invisible`)}};export{s as default}; -//# sourceMappingURL=DjTnIwxs.min.js.map \ No newline at end of file +`))o(e)}this.messages.push({role:`assistant`,content:a})}catch(t){s.showError(e,t)}finally{r.classList.remove(`typing`),this.controller=void 0,this.updateMoreButton(e)}}static showError(e,n){console.error(`Error loading AI summary:`,n);let r=t.translations?.error_loading_ai_summary??`Error loading the AI summary`,i=n instanceof Error&&n.message?` (${n.message})`:``,a=Object.assign(document.createElement(`div`),{textContent:`${r}${i}`,className:`dialog-error`});a.setAttribute(`role`,`alert`),(e.querySelector(`.ai-summary-answers`)??e).append(a)}updateMoreButton(e){let t=e.querySelector(`.ai-summary-body`),n=e.querySelector(`.ai-summary-more`);t&&n&&(!t.classList.contains(`collapsed`)||t.scrollHeight>t.clientHeight+4)&&n.classList.remove(`invisible`)}};export{s as default}; +//# sourceMappingURL=DBBLNmaq.min.js.map \ No newline at end of file diff --git a/searx/static/themes/simple/chunk/DBBLNmaq.min.js.map b/searx/static/themes/simple/chunk/DBBLNmaq.min.js.map new file mode 100644 index 000000000..fd629502c --- /dev/null +++ b/searx/static/themes/simple/chunk/DBBLNmaq.min.js.map @@ -0,0 +1 @@ +{"version":3,"file":"DBBLNmaq.min.js","names":[],"sources":["../../../../../client/simple/src/js/plugin/AiSummary.ts"],"sourcesContent":["// SPDX-License-Identifier: AGPL-3.0-or-later\n\nimport { Plugin } from \"../Plugin.ts\";\nimport { settings } from \"../toolkit.ts\";\nimport { assertElement } from \"../util/assertElement.ts\";\n\ntype Message = { role: \"user\" | \"assistant\"; content: string };\ntype ContextItem = { title: string; url: string; snippet: string };\ntype StreamLine = { delta?: string; done?: boolean; model?: string; error?: string };\n\n// keep in sync with the server side defaults (searx/ai_summary.py)\nconst MAX_CONTEXT_ITEMS = 5;\nconst MAX_HISTORY_MESSAGES = 12;\nconst MAX_TITLE_LENGTH = 300;\nconst MAX_SNIPPET_LENGTH = 1000;\n\n/**\n * Fills the AI summary placeholder (rendered by the ai_summary plugin of the\n * server) by streaming an answer from the /ai_summary endpoint, with an\n * expand button and a follow-up question chat.\n */\nexport default class AiSummary extends Plugin {\n private readonly messages: Message[] = [];\n private context: ContextItem[] = [];\n private controller: AbortController | undefined;\n\n public constructor() {\n super(\"ai_summary\");\n }\n\n protected async run(): Promise {\n const box = document.getElementById(\"ai_summary\");\n if (!box) return;\n\n try {\n const { query } = box.dataset;\n if (!query) return;\n\n if (box.dataset.grounding === \"1\") {\n this.context = AiSummary.collectContext();\n }\n\n this.wireControls(box);\n\n this.messages.push({ role: \"user\", content: query });\n await this.exchange(box);\n } catch (error) {\n // never fail silently, always leave a message in the summary box\n AiSummary.showError(box, error);\n }\n }\n\n protected async post(): Promise {\n // noop\n }\n\n /**\n * Scrape the top search results from the DOM as grounding context.\n */\n private static collectContext(): ContextItem[] {\n const items: ContextItem[] = [];\n for (const article of document.querySelectorAll(\"#urls article.result\")) {\n if (items.length >= MAX_CONTEXT_ITEMS) break;\n\n const link = article.querySelector(\"h3 a\");\n if (!link) continue;\n\n items.push({\n title: (link.textContent ?? \"\").trim().slice(0, MAX_TITLE_LENGTH),\n url: link.href,\n snippet: (article.querySelector(\".content\")?.textContent ?? \"\").trim().slice(0, MAX_SNIPPET_LENGTH)\n });\n }\n return items;\n }\n\n private wireControls(box: HTMLElement): void {\n const body = box.querySelector(\".ai-summary-body\");\n const moreButton = box.querySelector(\".ai-summary-more\");\n const followupForm = box.querySelector(\".ai-summary-followup\");\n assertElement(body);\n assertElement(moreButton);\n assertElement(followupForm);\n\n moreButton.addEventListener(\"click\", () => {\n const collapsed = body.classList.toggle(\"collapsed\");\n moreButton.textContent = collapsed\n ? (moreButton.dataset.btnTextCollapsed ?? \"\")\n : (moreButton.dataset.btnTextNotCollapsed ?? \"\");\n followupForm.classList.toggle(\"invisible\", collapsed);\n });\n\n followupForm.addEventListener(\"submit\", (event: Event) => {\n event.preventDefault();\n\n const input = followupForm.querySelector(\"input\");\n assertElement(input);\n\n const question = input.value.trim();\n if (!question || this.controller) return;\n\n input.value = \"\";\n this.messages.push({ role: \"user\", content: question });\n // the server rejects too long histories, drop the oldest messages\n this.messages.splice(0, this.messages.length - (MAX_HISTORY_MESSAGES - 1));\n\n const questionElement = Object.assign(document.createElement(\"p\"), {\n textContent: question,\n className: \"ai-summary-question\"\n });\n box.querySelector(\".ai-summary-answers\")?.append(questionElement);\n\n void this.exchange(box);\n });\n }\n\n /**\n * Send the message history to /ai_summary and stream the answer into a new\n * block in the answer area.\n */\n private async exchange(box: HTMLElement): Promise {\n const answers = box.querySelector(\".ai-summary-answers\");\n assertElement(answers);\n\n const block = Object.assign(document.createElement(\"p\"), {\n className: \"ai-summary-content typing\"\n });\n answers.append(block);\n\n const controller = new AbortController();\n this.controller = controller;\n\n let text = \"\";\n const handleLine = (line: string): void => {\n if (!line.trim()) return;\n\n const data = JSON.parse(line) as StreamLine;\n if (data.error) {\n throw new Error(data.error);\n }\n if (data.delta) {\n text += data.delta;\n block.textContent = text;\n this.updateMoreButton(box);\n }\n if (data.done && data.model) {\n const modelElement = box.querySelector(\".ai-summary-model\");\n if (modelElement) modelElement.textContent = data.model;\n }\n };\n\n try {\n const res = await fetch(\"./ai_summary\", {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ messages: this.messages, context: this.context }),\n signal: controller.signal\n });\n if (!res.ok) {\n // the status is shown to the user: it is often the only thing\n // available to diagnose a failure on a device without dev tools\n throw new Error(`HTTP ${res.status}`);\n }\n\n if (res.body) {\n const reader = res.body.getReader();\n const decoder = new TextDecoder();\n let buffer = \"\";\n\n for (;;) {\n // biome-ignore lint/performance/noAwaitInLoops: chunks of a stream are read sequentially\n const { done, value } = await reader.read();\n if (done) break;\n\n buffer += decoder.decode(value, { stream: true });\n const lines = buffer.split(\"\\n\");\n buffer = lines.pop() ?? \"\";\n for (const line of lines) handleLine(line);\n }\n handleLine(buffer);\n } else {\n // some webviews (e.g. Brave iOS) wrap fetch and don't expose a\n // streaming body: no typing effect, render the answer in one go\n const body = await res.text();\n for (const line of body.split(\"\\n\")) handleLine(line);\n }\n this.messages.push({ role: \"assistant\", content: text });\n } catch (error) {\n AiSummary.showError(box, error);\n } finally {\n block.classList.remove(\"typing\");\n this.controller = undefined;\n this.updateMoreButton(box);\n }\n }\n\n private static showError(box: HTMLElement, error: unknown): void {\n console.error(\"Error loading AI summary:\", error);\n\n const message = settings.translations?.error_loading_ai_summary ?? \"Error loading the AI summary\";\n const reason = error instanceof Error && error.message ? ` (${error.message})` : \"\";\n\n const errorElement = Object.assign(document.createElement(\"div\"), {\n textContent: `${message}${reason}`,\n className: \"dialog-error\"\n });\n errorElement.setAttribute(\"role\", \"alert\");\n (box.querySelector(\".ai-summary-answers\") ?? box).append(errorElement);\n }\n\n /**\n * Show the expand button as soon as the (collapsed) body overflows.\n */\n private updateMoreButton(box: HTMLElement): void {\n const body = box.querySelector(\".ai-summary-body\");\n const moreButton = box.querySelector(\".ai-summary-more\");\n if (!(body && moreButton)) return;\n\n if (!body.classList.contains(\"collapsed\") || body.scrollHeight > body.clientHeight + 4) {\n moreButton.classList.remove(\"invisible\");\n }\n }\n}\n"],"mappings":"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"} \ No newline at end of file diff --git a/searx/static/themes/simple/chunk/DjTnIwxs.min.js.map b/searx/static/themes/simple/chunk/DjTnIwxs.min.js.map deleted file mode 100644 index e8960c896..000000000 --- a/searx/static/themes/simple/chunk/DjTnIwxs.min.js.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"DjTnIwxs.min.js","names":[],"sources":["../../../../../client/simple/src/js/plugin/AiSummary.ts"],"sourcesContent":["// SPDX-License-Identifier: AGPL-3.0-or-later\n\nimport { Plugin } from \"../Plugin.ts\";\nimport { settings } from \"../toolkit.ts\";\nimport { assertElement } from \"../util/assertElement.ts\";\n\ntype Message = { role: \"user\" | \"assistant\"; content: string };\ntype ContextItem = { title: string; url: string; snippet: string };\ntype StreamLine = { delta?: string; done?: boolean; model?: string; error?: string };\n\n// keep in sync with the server side defaults (searx/ai_summary.py)\nconst MAX_CONTEXT_ITEMS = 5;\nconst MAX_HISTORY_MESSAGES = 12;\nconst MAX_TITLE_LENGTH = 300;\nconst MAX_SNIPPET_LENGTH = 1000;\n\n/**\n * Fills the AI summary placeholder (rendered by the ai_summary plugin of the\n * server) by streaming an answer from the /ai_summary endpoint, with an\n * expand button and a follow-up question chat.\n */\nexport default class AiSummary extends Plugin {\n private readonly messages: Message[] = [];\n private context: ContextItem[] = [];\n private controller: AbortController | undefined;\n\n public constructor() {\n super(\"ai_summary\");\n }\n\n protected async run(): Promise {\n const box = document.getElementById(\"ai_summary\");\n if (!box) return;\n\n try {\n const { query } = box.dataset;\n if (!query) return;\n\n if (box.dataset.grounding === \"1\") {\n this.context = AiSummary.collectContext();\n }\n\n this.wireControls(box);\n\n this.messages.push({ role: \"user\", content: query });\n await this.exchange(box);\n } catch (error) {\n // never fail silently, always leave a message in the summary box\n AiSummary.showError(box, error);\n }\n }\n\n protected async post(): Promise {\n // noop\n }\n\n /**\n * Scrape the top search results from the DOM as grounding context.\n */\n private static collectContext(): ContextItem[] {\n const items: ContextItem[] = [];\n for (const article of document.querySelectorAll(\"#urls article.result\")) {\n if (items.length >= MAX_CONTEXT_ITEMS) break;\n\n const link = article.querySelector(\"h3 a\");\n if (!link) continue;\n\n items.push({\n title: (link.textContent ?? \"\").trim().slice(0, MAX_TITLE_LENGTH),\n url: link.href,\n snippet: (article.querySelector(\".content\")?.textContent ?? \"\").trim().slice(0, MAX_SNIPPET_LENGTH)\n });\n }\n return items;\n }\n\n private wireControls(box: HTMLElement): void {\n const body = box.querySelector(\".ai-summary-body\");\n const moreButton = box.querySelector(\".ai-summary-more\");\n const followupForm = box.querySelector(\".ai-summary-followup\");\n assertElement(body);\n assertElement(moreButton);\n assertElement(followupForm);\n\n moreButton.addEventListener(\"click\", () => {\n const collapsed = body.classList.toggle(\"collapsed\");\n moreButton.textContent = collapsed\n ? (moreButton.dataset.btnTextCollapsed ?? \"\")\n : (moreButton.dataset.btnTextNotCollapsed ?? \"\");\n followupForm.classList.toggle(\"invisible\", collapsed);\n });\n\n followupForm.addEventListener(\"submit\", (event: Event) => {\n event.preventDefault();\n\n const input = followupForm.querySelector(\"input\");\n assertElement(input);\n\n const question = input.value.trim();\n if (!question || this.controller) return;\n\n input.value = \"\";\n this.messages.push({ role: \"user\", content: question });\n // the server rejects too long histories, drop the oldest messages\n this.messages.splice(0, this.messages.length - (MAX_HISTORY_MESSAGES - 1));\n\n const questionElement = Object.assign(document.createElement(\"p\"), {\n textContent: question,\n className: \"ai-summary-question\"\n });\n box.querySelector(\".ai-summary-answers\")?.append(questionElement);\n\n void this.exchange(box);\n });\n }\n\n /**\n * Send the message history to /ai_summary and stream the answer into a new\n * block in the answer area.\n */\n private async exchange(box: HTMLElement): Promise {\n const answers = box.querySelector(\".ai-summary-answers\");\n assertElement(answers);\n\n const block = Object.assign(document.createElement(\"p\"), {\n className: \"ai-summary-content typing\"\n });\n answers.append(block);\n\n const controller = new AbortController();\n this.controller = controller;\n\n let text = \"\";\n const handleLine = (line: string): void => {\n if (!line.trim()) return;\n\n const data = JSON.parse(line) as StreamLine;\n if (data.error) {\n throw new Error(data.error);\n }\n if (data.delta) {\n text += data.delta;\n block.textContent = text;\n this.updateMoreButton(box);\n }\n if (data.done && data.model) {\n const modelElement = box.querySelector(\".ai-summary-model\");\n if (modelElement) modelElement.textContent = data.model;\n }\n };\n\n try {\n const res = await fetch(\"./ai_summary\", {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n body: JSON.stringify({ messages: this.messages, context: this.context }),\n signal: controller.signal\n });\n if (!res.ok) {\n throw new Error(res.statusText);\n }\n\n if (res.body) {\n const reader = res.body.getReader();\n const decoder = new TextDecoder();\n let buffer = \"\";\n\n for (;;) {\n // biome-ignore lint/performance/noAwaitInLoops: chunks of a stream are read sequentially\n const { done, value } = await reader.read();\n if (done) break;\n\n buffer += decoder.decode(value, { stream: true });\n const lines = buffer.split(\"\\n\");\n buffer = lines.pop() ?? \"\";\n for (const line of lines) handleLine(line);\n }\n handleLine(buffer);\n } else {\n // some webviews (e.g. Brave iOS) wrap fetch and don't expose a\n // streaming body: no typing effect, render the answer in one go\n const body = await res.text();\n for (const line of body.split(\"\\n\")) handleLine(line);\n }\n this.messages.push({ role: \"assistant\", content: text });\n } catch (error) {\n AiSummary.showError(box, error);\n } finally {\n block.classList.remove(\"typing\");\n this.controller = undefined;\n this.updateMoreButton(box);\n }\n }\n\n private static showError(box: HTMLElement, error: unknown): void {\n console.error(\"Error loading AI summary:\", error);\n\n const errorElement = Object.assign(document.createElement(\"div\"), {\n textContent: settings.translations?.error_loading_ai_summary ?? \"Error loading the AI summary\",\n className: \"dialog-error\"\n });\n errorElement.setAttribute(\"role\", \"alert\");\n (box.querySelector(\".ai-summary-answers\") ?? box).append(errorElement);\n }\n\n /**\n * Show the expand button as soon as the (collapsed) body overflows.\n */\n private updateMoreButton(box: HTMLElement): void {\n const body = box.querySelector(\".ai-summary-body\");\n const moreButton = box.querySelector(\".ai-summary-more\");\n if (!(body && moreButton)) return;\n\n if (!body.classList.contains(\"collapsed\") || body.scrollHeight > body.clientHeight + 4) {\n moreButton.classList.remove(\"invisible\");\n }\n }\n}\n"],"mappings":"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"} \ No newline at end of file diff --git a/searx/static/themes/simple/manifest.json b/searx/static/themes/simple/manifest.json index bb2ab3a7c..f01d418f5 100644 --- a/searx/static/themes/simple/manifest.json +++ b/searx/static/themes/simple/manifest.json @@ -78,7 +78,7 @@ ] }, "src/js/plugin/AiSummary.ts": { - "file": "chunk/DjTnIwxs.min.js", + "file": "chunk/DBBLNmaq.min.js", "name": "aisummary", "src": "src/js/plugin/AiSummary.ts", "isDynamicEntry": true, diff --git a/searx/static/themes/simple/sxng-core.min.js b/searx/static/themes/simple/sxng-core.min.js index badb7746a..ce686a1b9 100644 --- a/searx/static/themes/simple/sxng-core.min.js +++ b/searx/static/themes/simple/sxng-core.min.js @@ -1,3 +1,3 @@ -const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["./chunk/BnnvKC7b.min.js","./sxng-mapview.min.css","./chunk/DpvWr1cn.min.js","./chunk/DK4yUVpy.min.js","./chunk/DcK-mo-Y.min.js","./chunk/C8c7HJzp.min.js","./chunk/DjTnIwxs.min.js","./chunk/C93hSkpT.min.js","./chunk/5Ako-qGW.min.js","./chunk/DvCYLbJr.min.js","./chunk/od7pNHfk.min.js","./chunk/e2-9fzwE.min.js"])))=>i.map(i=>d[i]); -var e=class{id;constructor(e){this.id=e,queueMicrotask(()=>this.invoke())}async invoke(){try{console.debug(`[PLUGIN] ${this.id}: Running...`);let e=await this.run();if(!e)return;console.debug(`[PLUGIN] ${this.id}: Running post-exec...`),await this.post(e)}catch(e){console.error(`[PLUGIN] ${this.id}:`,e)}finally{console.debug(`[PLUGIN] ${this.id}: Done.`)}}},t={index:`index`,results:`results`,preferences:`preferences`,unknown:`unknown`},n={closeDetail:void 0,scrollPageToSelected:void 0,selectImage:void 0,selectNext:void 0,selectPrevious:void 0},r=()=>{let e=document.querySelector(`meta[name="endpoint"]`)?.getAttribute(`content`);return e&&e in t?e:t.unknown},i=()=>{let e=document.querySelector(`script[client_settings]`)?.getAttribute(`client_settings`);if(!e)return{};try{return JSON.parse(atob(e))}catch(e){return console.error(`Failed to load client_settings:`,e),{}}},a=async(e,t,n)=>{let r=new AbortController,i=setTimeout(()=>r.abort(),n?.timeout??3e4),a=await fetch(t,{body:n?.body,method:e,signal:r.signal}).finally(()=>clearTimeout(i));if(!a.ok)throw Error(a.statusText);return a},o=(e,t,n,r)=>{if(typeof t!=`string`){t.addEventListener(e,n,r);return}document.addEventListener(e,e=>{for(let r of e.composedPath())if(r instanceof HTMLElement&&r.matches(t)){try{n.call(r,e)}catch(e){console.error(e)}break}},r)},s=(e,t)=>{for(let e of t?.on??[])if(!e)return;document.readyState===`loading`?o(`DOMContentLoaded`,document,e,{once:!0}):e()},c=r(),l=i(),u=(e,t)=>{d(t)&&e()},d=e=>{switch(e.on){case`global`:return!0;case`endpoint`:return!!e.where.includes(c)}},f=`modulepreload`,p=function(e,t){return new URL(e,t).href},m={},h=function(e,t,n){let r=Promise.resolve();if(t&&t.length>0){let e=document.getElementsByTagName(`link`),i=document.querySelector(`meta[property=csp-nonce]`),a=i?.nonce||i?.getAttribute(`nonce`);function o(e){return Promise.all(e.map(e=>Promise.resolve(e).then(e=>({status:`fulfilled`,value:e}),e=>({status:`rejected`,reason:e}))))}function s(e){return import.meta.resolve?import.meta.resolve(e):new URL(e,import.meta.url).href}r=o(t.map(t=>{if(t=p(t,n),t=s(t),t in m)return;m[t]=!0;let r=t.endsWith(`.css`);for(let n=e.length-1;n>=0;n--){let i=e[n];if(i.href===t&&(!r||i.rel===`stylesheet`))return}let i=document.createElement(`link`);if(i.rel=r?`stylesheet`:f,r||(i.as=`script`),i.crossOrigin=``,i.href=t,a&&i.setAttribute(`nonce`,a),document.head.appendChild(i),r)return new Promise((e,n)=>{i.addEventListener(`load`,e),i.addEventListener(`error`,()=>n(Error(`Unable to preload CSS for ${t}`)))})}))}function i(e){let t=new Event(`vite:preloadError`,{cancelable:!0});if(t.payload=e,window.dispatchEvent(t),!t.defaultPrevented)throw e}return r.then(t=>{for(let e of t||[])e.status===`rejected`&&i(e.reason);return e().catch(i)})};s(()=>{document.documentElement.classList.remove(`no-js`),document.documentElement.classList.add(`js`),o(`click`,`.close`,function(){this.parentNode?.classList.add(`invisible`)}),o(`click`,`.searxng_init_map`,async function(e){e.preventDefault(),this.classList.remove(`searxng_init_map`),u(()=>h(async()=>{let{default:e}=await import(`./chunk/BnnvKC7b.min.js`);return{default:e}},__vite__mapDeps([0,1]),import.meta.url).then(({default:e})=>new e(this)),{on:`endpoint`,where:[t.results]})}),l.plugins?.includes(`infiniteScroll`)&&u(()=>h(async()=>{let{default:e}=await import(`./chunk/DpvWr1cn.min.js`);return{default:e}},__vite__mapDeps([2,3,4]),import.meta.url).then(({default:e})=>new e),{on:`endpoint`,where:[t.results]}),l.plugins?.includes(`calculator`)&&u(()=>h(async()=>{let{default:e}=await import(`./chunk/C8c7HJzp.min.js`);return{default:e}},__vite__mapDeps([5,4,3]),import.meta.url).then(({default:e})=>new e),{on:`endpoint`,where:[t.results]}),l.plugins?.includes(`ai_summary`)&&u(()=>h(async()=>{let{default:e}=await import(`./chunk/DjTnIwxs.min.js`);return{default:e}},__vite__mapDeps([6,3]),import.meta.url).then(({default:e})=>new e),{on:`endpoint`,where:[t.results]})}),s(()=>{h(()=>import(`./chunk/C93hSkpT.min.js`),__vite__mapDeps([7,3]),import.meta.url),h(()=>import(`./chunk/5Ako-qGW.min.js`),__vite__mapDeps([8,4,3]),import.meta.url),l.autocomplete&&h(()=>import(`./chunk/DvCYLbJr.min.js`),__vite__mapDeps([9,3]),import.meta.url)},{on:[c===t.index]}),s(()=>{h(()=>import(`./chunk/C93hSkpT.min.js`),__vite__mapDeps([7,3]),import.meta.url),h(()=>import(`./chunk/od7pNHfk.min.js`),__vite__mapDeps([10,3]),import.meta.url),h(()=>import(`./chunk/5Ako-qGW.min.js`),__vite__mapDeps([8,4,3]),import.meta.url),l.autocomplete&&h(()=>import(`./chunk/DvCYLbJr.min.js`),__vite__mapDeps([9,3]),import.meta.url)},{on:[c===t.results]}),s(()=>{h(()=>import(`./chunk/e2-9fzwE.min.js`),__vite__mapDeps([11,3]),import.meta.url)},{on:[c===t.preferences]});export{e as a,l as i,o as n,n as r,a as t}; +const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=["./chunk/BnnvKC7b.min.js","./sxng-mapview.min.css","./chunk/DpvWr1cn.min.js","./chunk/DK4yUVpy.min.js","./chunk/DcK-mo-Y.min.js","./chunk/C8c7HJzp.min.js","./chunk/DBBLNmaq.min.js","./chunk/C93hSkpT.min.js","./chunk/5Ako-qGW.min.js","./chunk/DvCYLbJr.min.js","./chunk/od7pNHfk.min.js","./chunk/e2-9fzwE.min.js"])))=>i.map(i=>d[i]); +var e=class{id;constructor(e){this.id=e,queueMicrotask(()=>this.invoke())}async invoke(){try{console.debug(`[PLUGIN] ${this.id}: Running...`);let e=await this.run();if(!e)return;console.debug(`[PLUGIN] ${this.id}: Running post-exec...`),await this.post(e)}catch(e){console.error(`[PLUGIN] ${this.id}:`,e)}finally{console.debug(`[PLUGIN] ${this.id}: Done.`)}}},t={index:`index`,results:`results`,preferences:`preferences`,unknown:`unknown`},n={closeDetail:void 0,scrollPageToSelected:void 0,selectImage:void 0,selectNext:void 0,selectPrevious:void 0},r=()=>{let e=document.querySelector(`meta[name="endpoint"]`)?.getAttribute(`content`);return e&&e in t?e:t.unknown},i=()=>{let e=document.querySelector(`script[client_settings]`)?.getAttribute(`client_settings`);if(!e)return{};try{return JSON.parse(atob(e))}catch(e){return console.error(`Failed to load client_settings:`,e),{}}},a=async(e,t,n)=>{let r=new AbortController,i=setTimeout(()=>r.abort(),n?.timeout??3e4),a=await fetch(t,{body:n?.body,method:e,signal:r.signal}).finally(()=>clearTimeout(i));if(!a.ok)throw Error(a.statusText);return a},o=(e,t,n,r)=>{if(typeof t!=`string`){t.addEventListener(e,n,r);return}document.addEventListener(e,e=>{for(let r of e.composedPath())if(r instanceof HTMLElement&&r.matches(t)){try{n.call(r,e)}catch(e){console.error(e)}break}},r)},s=(e,t)=>{for(let e of t?.on??[])if(!e)return;document.readyState===`loading`?o(`DOMContentLoaded`,document,e,{once:!0}):e()},c=r(),l=i(),u=(e,t)=>{d(t)&&e()},d=e=>{switch(e.on){case`global`:return!0;case`endpoint`:return!!e.where.includes(c)}},f=`modulepreload`,p=function(e,t){return new URL(e,t).href},m={},h=function(e,t,n){let r=Promise.resolve();if(t&&t.length>0){let e=document.getElementsByTagName(`link`),i=document.querySelector(`meta[property=csp-nonce]`),a=i?.nonce||i?.getAttribute(`nonce`);function o(e){return Promise.all(e.map(e=>Promise.resolve(e).then(e=>({status:`fulfilled`,value:e}),e=>({status:`rejected`,reason:e}))))}function s(e){return import.meta.resolve?import.meta.resolve(e):new URL(e,import.meta.url).href}r=o(t.map(t=>{if(t=p(t,n),t=s(t),t in m)return;m[t]=!0;let r=t.endsWith(`.css`);for(let n=e.length-1;n>=0;n--){let i=e[n];if(i.href===t&&(!r||i.rel===`stylesheet`))return}let i=document.createElement(`link`);if(i.rel=r?`stylesheet`:f,r||(i.as=`script`),i.crossOrigin=``,i.href=t,a&&i.setAttribute(`nonce`,a),document.head.appendChild(i),r)return new Promise((e,n)=>{i.addEventListener(`load`,e),i.addEventListener(`error`,()=>n(Error(`Unable to preload CSS for ${t}`)))})}))}function i(e){let t=new Event(`vite:preloadError`,{cancelable:!0});if(t.payload=e,window.dispatchEvent(t),!t.defaultPrevented)throw e}return r.then(t=>{for(let e of t||[])e.status===`rejected`&&i(e.reason);return e().catch(i)})};s(()=>{document.documentElement.classList.remove(`no-js`),document.documentElement.classList.add(`js`),o(`click`,`.close`,function(){this.parentNode?.classList.add(`invisible`)}),o(`click`,`.searxng_init_map`,async function(e){e.preventDefault(),this.classList.remove(`searxng_init_map`),u(()=>h(async()=>{let{default:e}=await import(`./chunk/BnnvKC7b.min.js`);return{default:e}},__vite__mapDeps([0,1]),import.meta.url).then(({default:e})=>new e(this)),{on:`endpoint`,where:[t.results]})}),l.plugins?.includes(`infiniteScroll`)&&u(()=>h(async()=>{let{default:e}=await import(`./chunk/DpvWr1cn.min.js`);return{default:e}},__vite__mapDeps([2,3,4]),import.meta.url).then(({default:e})=>new e),{on:`endpoint`,where:[t.results]}),l.plugins?.includes(`calculator`)&&u(()=>h(async()=>{let{default:e}=await import(`./chunk/C8c7HJzp.min.js`);return{default:e}},__vite__mapDeps([5,4,3]),import.meta.url).then(({default:e})=>new e),{on:`endpoint`,where:[t.results]}),l.plugins?.includes(`ai_summary`)&&u(()=>h(async()=>{let{default:e}=await import(`./chunk/DBBLNmaq.min.js`);return{default:e}},__vite__mapDeps([6,3]),import.meta.url).then(({default:e})=>new e),{on:`endpoint`,where:[t.results]})}),s(()=>{h(()=>import(`./chunk/C93hSkpT.min.js`),__vite__mapDeps([7,3]),import.meta.url),h(()=>import(`./chunk/5Ako-qGW.min.js`),__vite__mapDeps([8,4,3]),import.meta.url),l.autocomplete&&h(()=>import(`./chunk/DvCYLbJr.min.js`),__vite__mapDeps([9,3]),import.meta.url)},{on:[c===t.index]}),s(()=>{h(()=>import(`./chunk/C93hSkpT.min.js`),__vite__mapDeps([7,3]),import.meta.url),h(()=>import(`./chunk/od7pNHfk.min.js`),__vite__mapDeps([10,3]),import.meta.url),h(()=>import(`./chunk/5Ako-qGW.min.js`),__vite__mapDeps([8,4,3]),import.meta.url),l.autocomplete&&h(()=>import(`./chunk/DvCYLbJr.min.js`),__vite__mapDeps([9,3]),import.meta.url)},{on:[c===t.results]}),s(()=>{h(()=>import(`./chunk/e2-9fzwE.min.js`),__vite__mapDeps([11,3]),import.meta.url)},{on:[c===t.preferences]});export{e as a,l as i,o as n,n as r,a as t}; //# sourceMappingURL=sxng-core.min.js.map \ No newline at end of file