fix: 'MainResult' object has no attribute 'get'

This commit is contained in:
cra88y/pc
2026-04-12 20:52:41 -05:00
parent 6947257575
commit fa59a82f43
+32 -21
View File
@@ -183,7 +183,7 @@ INTERACTIVE_HTML = '''
</div> </div>
''' '''
CITATION_HELPER_JS = ''' CITATION_HELPER_JS = r'''
function renderCitations(text, urls) { function renderCitations(text, urls) {
const fragment = document.createDocumentFragment(); const fragment = document.createDocumentFragment();
const re = /\[(\d{1,2}(?:\s*,\s*\d{1,2})*)\]/g; const re = /\[(\d{1,2}(?:\s*,\s*\d{1,2})*)\]/g;
@@ -237,7 +237,7 @@ CITATION_HELPER_JS = '''
} }
''' '''
INTERACTIVE_JS = ''' INTERACTIVE_JS = r'''
const footer = document.getElementById('sxng-footer'); const footer = document.getElementById('sxng-footer');
const input = document.getElementById('sxng-action-input'); const input = document.getElementById('sxng-action-input');
// Inherited from outer scope: box, data, conversation // Inherited from outer scope: box, data, conversation
@@ -572,12 +572,22 @@ class SXNGPlugin(Plugin):
results = [] results = []
limit = self.context_deep_count + self.context_shallow_count limit = self.context_deep_count + self.context_shallow_count
for r in raw_results[:limit]: for r in raw_results[:limit]:
results.append({ # Handle both MainResult (attribute access) and LegacyResult (dict access)
'title': r.get('title', ''), if hasattr(r, 'title'):
'content': r.get('content', ''), results.append({
'url': r.get('url', ''), 'title': getattr(r, 'title', ''),
'publishedDate': r.get('publishedDate', '') 'content': getattr(r, 'content', ''),
}) 'url': getattr(r, 'url', ''),
'publishedDate': getattr(r, 'publishedDate', '')
})
else:
# Legacy dictionary-style access
results.append({
'title': r.get('title', ''),
'content': r.get('content', ''),
'url': r.get('url', ''),
'publishedDate': r.get('publishedDate', '')
})
# SearXNG already merges infoboxes by ID - take first with full content # SearXNG already merges infoboxes by ID - take first with full content
infoboxes = [] infoboxes = []
@@ -875,9 +885,7 @@ class SXNGPlugin(Plugin):
generator = stream_gemini if self.is_gemini else stream_openai_compatible generator = stream_gemini if self.is_gemini else stream_openai_compatible
# If configured, force-unload Ollama model right after finishing the stream. # Force-unload Ollama model after stream via keep_alive=0
# This uses the native /api/chat endpoint with keep_alive=0.
if self.provider == 'ollama' and getattr(self, 'ollama_unload_after', False): if self.provider == 'ollama' and getattr(self, 'ollama_unload_after', False):
@@ -934,13 +942,13 @@ class SXNGPlugin(Plugin):
# Deep sources: full content # Deep sources: full content
deep_lines = [] deep_lines = []
for i, r in enumerate(raw_results[:self.context_deep_count]): for i, r in enumerate(raw_results[:self.context_deep_count]):
url = r.get('url', '') url = getattr(r, 'url', '') if hasattr(r, 'url') else r.get('url', '')
result_urls.append(url) result_urls.append(url)
domain = urlparse(url).netloc.replace('www.', '') domain = urlparse(url).netloc.replace('www.', '')
date = r.get('publishedDate') date = getattr(r, 'publishedDate', '') if hasattr(r, 'publishedDate') else r.get('publishedDate')
date_str = f" ({date})" if date else "" date_str = f" ({date})" if date else ""
title = (r.get('title') or "").replace('\n', ' ').strip() title = (getattr(r, 'title', '') if hasattr(r, 'title') else r.get('title') or "").replace('\n', ' ').strip()
content = str(r.get('content', '')).replace('\n', ' ').strip()[:800] content = str(getattr(r, 'content', '') if hasattr(r, 'content') else r.get('content', '')).replace('\n', ' ').strip()[:800]
idx = i + 1 + offset idx = i + 1 + offset
deep_lines.append(f"[{idx}] {domain}{date_str}: {title}: {content}") deep_lines.append(f"[{idx}] {domain}{date_str}: {title}: {content}")
@@ -953,10 +961,10 @@ class SXNGPlugin(Plugin):
start_idx = self.context_deep_count start_idx = self.context_deep_count
end_idx = self.context_deep_count + self.context_shallow_count end_idx = self.context_deep_count + self.context_shallow_count
for i, r in enumerate(raw_results[start_idx:end_idx]): for i, r in enumerate(raw_results[start_idx:end_idx]):
url = r.get('url', '') url = getattr(r, 'url', '') if hasattr(r, 'url') else r.get('url', '')
result_urls.append(url) result_urls.append(url)
domain = urlparse(url).netloc.replace('www.', '') domain = urlparse(url).netloc.replace('www.', '')
title = (r.get('title') or '').replace('\n', ' ').strip()[:60] title = (getattr(r, 'title', '') if hasattr(r, 'title') else r.get('title') or '').replace('\n', ' ').strip()[:60]
idx = i + 1 + start_idx + offset idx = i + 1 + start_idx + offset
shallow_lines.append(f"[{idx}] {domain}: {title}") shallow_lines.append(f"[{idx}] {domain}: {title}")
@@ -1002,7 +1010,7 @@ class SXNGPlugin(Plugin):
js_q = json.dumps(q_clean) js_q = json.dumps(q_clean)
js_lang = json.dumps(lang) js_lang = json.dumps(lang)
total_context_count = self.context_deep_count + self.context_shallow_count total_context_count = self.context_deep_count + self.context_shallow_count
js_urls = json.dumps([r.get('url') for r in raw_results[:total_context_count]]) js_urls = json.dumps([getattr(r, 'url', '') if hasattr(r, 'url') else r.get('url', '') for r in raw_results[:total_context_count]])
is_interactive = self.interactive is_interactive = self.interactive
@@ -1015,7 +1023,7 @@ class SXNGPlugin(Plugin):
stream_q = 'overrideQ || q_init' if is_interactive else 'q_init' stream_q = 'overrideQ || q_init' if is_interactive else 'q_init'
stream_body = f'''prev_answer: prevAnswer''' if is_interactive else '' stream_body = f'''prev_answer: prevAnswer''' if is_interactive else ''
html_payload = f''' html_payload = rf'''
<article id="sxng-stream-box" class="answer" style="display:none; margin: 1rem 0;"> <article id="sxng-stream-box" class="answer" style="display:none; margin: 1rem 0;">
<style> <style>
@keyframes sxng-fade-pulse {{ @keyframes sxng-fade-pulse {{
@@ -1078,7 +1086,7 @@ class SXNGPlugin(Plugin):
{interactive_js_init} {interactive_js_init}
function synthesizeQuery(original, followup) {{ function synthesizeQuery(original, followup) {{
// combine with original for context, stripping generic question starters // Strip generic question starters
const cleanOrig = original.replace(/^(what|how|why|when|where|who|which|is|are|can|does|do)(\s+(is|are|do|does|can|to|a|an|the))?\s+/i, ''); const cleanOrig = original.replace(/^(what|how|why|when|where|who|which|is|are|can|does|do)(\s+(is|are|do|does|can|to|a|an|the))?\s+/i, '');
const origWords = cleanOrig.split(' ').slice(0, 12); const origWords = cleanOrig.split(' ').slice(0, 12);
return `${{origWords.join(' ')}} ${{followup}}`.trim(); return `${{origWords.join(' ')}} ${{followup}}`.trim();
@@ -1265,7 +1273,7 @@ class SXNGPlugin(Plugin):
}} }}
}} }}
// Warmup handshake // Warmup
fetch('/ai-stream', {{ fetch('/ai-stream', {{
method: 'POST', method: 'POST',
headers: {{'Content-Type': 'application/json'}}, headers: {{'Content-Type': 'application/json'}},
@@ -1282,3 +1290,6 @@ class SXNGPlugin(Plugin):
except Exception as e: except Exception as e:
logger.error(f"{PLUGIN_NAME}: {e}") logger.error(f"{PLUGIN_NAME}: {e}")
return results return results