diff --git a/gemini_flash.py b/gemini_flash.py
index a81d35e..ecf02f0 100644
--- a/gemini_flash.py
+++ b/gemini_flash.py
@@ -1,4 +1,4 @@
-import json, http.client, ssl, os, logging, base64, secrets, time, hashlib
+import json, http.client, ssl, os, logging, base64, time, hashlib
from flask import Response, request, abort
from searx.plugins import Plugin, PluginInfo
from searx.result_types import EngineResults
@@ -7,6 +7,10 @@ from markupsafe import Markup
logger = logging.getLogger(__name__)
+# Constants
+TOKEN_EXPIRY_SEC = 60
+CONNECTION_TIMEOUT_SEC = 30
+
class SXNGPlugin(Plugin):
id = "gemini_flash"
@@ -21,11 +25,21 @@ class SXNGPlugin(Plugin):
self.provider = os.getenv('LLM_PROVIDER', 'openrouter').lower()
self.api_key = os.getenv('OPENROUTER_API_KEY') if self.provider == 'openrouter' else os.getenv('GEMINI_API_KEY')
self.model = os.getenv('GEMINI_MODEL', 'gemma-3-27b-it') if self.provider == 'gemini' else os.getenv('OPENROUTER_MODEL', 'google/gemma-3-27b-it:free')
- self.max_tokens = int(os.getenv('GEMINI_MAX_TOKENS', 500))
- self.temperature = float(os.getenv('GEMINI_TEMPERATURE', 0.2))
+ try:
+ self.max_tokens = int(os.getenv('GEMINI_MAX_TOKENS', 500))
+ except ValueError:
+ self.max_tokens = 500
+ try:
+ self.temperature = float(os.getenv('GEMINI_TEMPERATURE', 0.2))
+ except ValueError:
+ self.temperature = 0.2
self.base_url = os.getenv('OPENROUTER_BASE_URL', 'openrouter.ai')
# Stable secret for multi-worker environments
- self.secret = os.getenv('SXNG_LLM_SECRET') or hashlib.sha256(self.api_key.encode()).hexdigest()
+ if self.api_key:
+ self.secret = os.getenv('SXNG_LLM_SECRET') or hashlib.sha256(self.api_key.encode()).hexdigest()
+ else:
+ self.secret = os.getenv('SXNG_LLM_SECRET', '')
+ logger.warning("Gemini Flash plugin: No API key configured, plugin will be inactive")
def init(self, app):
@app.route('/gemini-stream', methods=['POST'])
@@ -38,9 +52,10 @@ class SXNGPlugin(Plugin):
ts, sig = token.split('.', 1)
query_clean = q.strip()
expected = hashlib.sha256(f"{ts}{query_clean}{self.secret}".encode()).hexdigest()
- if sig != expected or (time.time() - float(ts)) > 60:
+ if sig != expected or (time.time() - float(ts)) > TOKEN_EXPIRY_SEC:
abort(403)
- except: abort(403)
+ except (ValueError, KeyError, AttributeError):
+ abort(403)
context_text = data.get('context', '')
if not self.api_key or not q:
@@ -60,7 +75,7 @@ class SXNGPlugin(Plugin):
host = "generativelanguage.googleapis.com"
path = f"/v1/models/{self.model}:streamGenerateContent?key={self.api_key}"
try:
- conn = http.client.HTTPSConnection(host, context=ssl.create_default_context())
+ conn = http.client.HTTPSConnection(host, timeout=CONNECTION_TIMEOUT_SEC, context=ssl.create_default_context())
payload = {"contents": [{"parts": [{"text": prompt}]}], "generationConfig": {"maxOutputTokens": self.max_tokens, "temperature": self.temperature}}
conn.request("POST", path, body=json.dumps(payload), headers={"Content-Type": "application/json"})
res = conn.getresponse()
@@ -93,7 +108,7 @@ class SXNGPlugin(Plugin):
def generate_openrouter():
try:
- conn = http.client.HTTPSConnection(self.base_url, context=ssl.create_default_context())
+ conn = http.client.HTTPSConnection(self.base_url, timeout=CONNECTION_TIMEOUT_SEC, context=ssl.create_default_context())
payload = {
"model": self.model,
"messages": [{"role": "user", "content": prompt}],
@@ -104,8 +119,8 @@ class SXNGPlugin(Plugin):
headers = {
"Authorization": f"Bearer {self.api_key}",
"Content-Type": "application/json",
- "HTTP-Referer": "https://github.com/cra88y/searxng-stream-gemini",
- "X-Title": "SearXNG Stream"
+ "HTTP-Referer": "https://github.com/searxng/searxng",
+ "X-Title": "SearXNG LLM Plugin"
}
conn.request("POST", "/api/v1/chat/completions", body=json.dumps(payload), headers=headers)
res = conn.getresponse()
@@ -128,7 +143,8 @@ class SXNGPlugin(Plugin):
obj, _ = decoder.raw_decode(data_str)
content = obj.get("choices", [{}])[0].get("delta", {}).get("content", "")
if content: yield content
- except: pass
+ except json.JSONDecodeError:
+ pass
conn.close()
except Exception as e: logger.error(f"OpenRouter Stream Exception: {e}")
@@ -138,83 +154,95 @@ class SXNGPlugin(Plugin):
def post_search(self, request, search) -> EngineResults:
results = EngineResults()
- if not self.active or not self.api_key or search.search_query.pageno > 1:
- return results
+ try:
+ if not self.active or not self.api_key or search.search_query.pageno > 1:
+ return results
- raw_results = search.result_container.get_ordered_results()
- context_list = [f"[{i+1}] {r.get('title')}: {r.get('content')}" for i, r in enumerate(raw_results[:6])]
- context_str = "\n".join(context_list)
+ raw_results = search.result_container.get_ordered_results()
+ context_list = [f"[{i+1}] {r.get('title')}: {r.get('content')}" for i, r in enumerate(raw_results[:6])]
+ context_str = "\n".join(context_list)
- # Stateless Handshake
- ts = str(int(time.time()))
- q_clean = search.search_query.query.strip()
- sig = hashlib.sha256(f"{ts}{q_clean}{self.secret}".encode()).hexdigest()
- tk = f"{ts}.{sig}"
+ # Stateless Handshake
+ ts = str(int(time.time()))
+ q_clean = search.search_query.query.strip()
+ sig = hashlib.sha256(f"{ts}{q_clean}{self.secret}".encode()).hexdigest()
+ tk = f"{ts}.{sig}"
- b64_context = base64.b64encode(context_str.encode('utf-8')).decode('utf-8')
- js_q = json.dumps(q_clean)
+ b64_context = base64.b64encode(context_str.encode('utf-8')).decode('utf-8')
+ js_q = json.dumps(q_clean)
- html_payload = f'''
-
-
-
- Thinking...
-
-
-
- '''
- search.result_container.answers.add(results.types.Answer(answer=Markup(html_payload)))
+ #sxng-stream-box {{
+ max-height: 0;
+ overflow: hidden;
+ transition: max-height 0.4s ease-out;
+ }}
+ #sxng-stream-box.sxng-open {{
+ max-height: 30em;
+ }}
+
+
+
+
+
+ '''
+ search.result_container.answers.add(results.types.Answer(answer=Markup(html_payload)))
+ except Exception as e:
+ logger.error(f"Gemini Flash plugin error: {e}")
return results