aboutsummaryrefslogtreecommitdiff
path: root/modules/core
diff options
context:
space:
mode:
authorAgando <roduccomm@gmail.com>2026-05-30 18:01:51 +0200
committerAgando <roduccomm@gmail.com>2026-05-30 18:01:51 +0200
commitd8bf7fa398d6d928325be9b5c64ca24875bfeef8 (patch)
tree49ee4b3586ad68c648d546e6831abc30d8576be5 /modules/core
parente3fb2d434c4f7317944b7c77781599528d766a39 (diff)
auto: 2026-05-30_18:00 changes
Diffstat (limited to 'modules/core')
-rw-r--r--modules/core/module-chatstudio.js44
-rw-r--r--modules/core/module-searxng.js65
2 files changed, 83 insertions, 26 deletions
diff --git a/modules/core/module-chatstudio.js b/modules/core/module-chatstudio.js
index 5777acf..762eae3 100644
--- a/modules/core/module-chatstudio.js
+++ b/modules/core/module-chatstudio.js
@@ -114,10 +114,16 @@ class ChatStudioModule {
window._csGlobalsInited = true;
}
- // Fetch HTML template
+ // Fetch HTML template (cached after first load)
+ if (this._overlayHtml) {
+ // Overlay div already exists (just hidden). No-op.
+ resolve();
+ return;
+ }
fetch('chat/chat-ui.html?v=4')
.then(r => r.text())
.then(html => {
+ this._overlayHtml = html; // cache for re-open
// Insert overlay into body if not already there
if (!document.getElementById('chat-overlay')) {
const temp = document.createElement('div');
@@ -190,10 +196,10 @@ class ChatStudioModule {
window.showToast('Chat overlay failed: ' + err.message);
}
- // Clean up any partial overlay DOM
+ // Clean up any partial overlay DOM — hide instead of remove so cached HTML path still works
const partialOverlay = document.getElementById('chat-overlay');
if (partialOverlay) {
- partialOverlay.remove();
+ partialOverlay.style.display = 'none';
}
// Reset state so user can retry
@@ -301,17 +307,35 @@ class ChatStudioModule {
el.style.height = Math.min(el.scrollHeight, 150) + 'px';
}
+ // Close sidebar when clicking outside on mobile
+ var backdrop = document.getElementById('sidebarBackdrop');
+ if (!backdrop) {
+ backdrop = document.createElement('div');
+ backdrop.id = 'sidebarBackdrop';
+ backdrop.style.cssText = 'position:fixed;top:0;left:0;right:0;bottom:0;z-index:99;display:none;background:transparent';
+ backdrop.onclick = function() { closeSidebar(); };
+ document.getElementById('chat-overlay')?.appendChild(backdrop);
+ }
+
function toggleSidebar() {
var s = document.getElementById('sidebar');
- if (s) s.classList.toggle('open');
- var b = document.getElementById('sidebarBackdrop');
- if (b) b.classList.toggle('open');
+ if (!s) return;
+ // On mobile (<700px) use .open, on desktop use .collapsed
+ var isMobile = window.innerWidth <= 700;
+ if (isMobile) {
+ s.classList.toggle('open');
+ } else {
+ s.classList.toggle('collapsed');
+ // Toggle backdrop on mobile regardless
+ }
+ if (backdrop) {
+ backdrop.style.display = s.classList.contains('open') || s.classList.contains('collapsed') ? 'block' : 'none';
+ }
}
function closeSidebar() {
var s = document.getElementById('sidebar');
- if (s) s.classList.remove('open');
- var b = document.getElementById('sidebarBackdrop');
- if (b) b.classList.remove('open');
+ if (s) { s.classList.remove('open'); s.classList.remove('collapsed'); }
+ if (backdrop) backdrop.style.display = 'none';
}
// ── Log System ──
@@ -403,7 +427,7 @@ class ChatStudioModule {
OLLAMA_API_KEY = document.getElementById('ollamaApiKey').value.trim();
DEFAULT_MODEL = document.getElementById('defaultModel').value.trim() || 'llama3.2';
USE_STREAMING = document.getElementById('streamingToggle').checked;
- API_BASE = document.getElementById('apiBase').value.trim() || '/api';
+ API_BASE = (document.getElementById('apiBase')?.value || '/api').trim();
try {
var settings = {
ollamaUrl: OLLAMA_URL,
diff --git a/modules/core/module-searxng.js b/modules/core/module-searxng.js
index f2c2e72..24db0f5 100644
--- a/modules/core/module-searxng.js
+++ b/modules/core/module-searxng.js
@@ -5,29 +5,41 @@ class SearXNGModule {
}
getHelpInfo() {
return {
- 'search': 'search <query> [--summarize] [--fetch] — Search using SearXNG; --summarize/-s adds AI overview, --fetch/-f fetches page content',
- 's': 's <query> [--summarize] [--fetch] — Alias for search',
+ 'search': 'search <query> [--summarize/-s] [--fetch/-f] [--images/-i] [--videos/-v] — Search using SearXNG; --summarize/-s adds AI overview, --fetch/-f fetches page content, --images/-i searches for images, --videos/-v searches for videos',
+ 's': 's <query> [--summarize] [--fetch] [--images] [--videos] — Alias for search',
+ 'images': 'images <query> — Search for images',
+ 'videos': 'videos <query> — Search for videos',
};
}
registerCommands() {
this.commands['search'] = (args) => this.search(args);
this.commands['s'] = (args) => this.search(args);
+ this.commands['images'] = (args) => this.search(args, 'images');
+ this.commands['videos'] = (args) => this.search(args, 'videos');
}
- async search(args) {
- if (!args[0]) {
- this.terminal.print("Usage: search <query> [--summarize] [--fetch]", "warning");
+ async search(args, forceCategory) {
+ if (!args[0] && !forceCategory) {
+ this.terminal.print("Usage: search <query> [--summarize] [--fetch] [--images] [--videos]", "warning");
this.terminal.print("Example: search quantum computing --summarize", "info");
+ this.terminal.print("Example: search cats --images", "info");
+ this.terminal.print("Example: search cats --videos", "info");
+ this.terminal.print("Shortcuts: images <query> or videos <query>", "info");
return;
}
const argsList = args.slice();
const summarize = argsList.includes('--summarize') || argsList.includes('-s');
const fetchContent = argsList.includes('--fetch') || argsList.includes('-f');
+ const wantImages = forceCategory === 'images' || argsList.includes('--images') || argsList.includes('-i');
+ const wantVideos = forceCategory === 'videos' || argsList.includes('--videos') || argsList.includes('-v');
const query = argsList.filter(a => !a.startsWith('-')).join(' ');
if (!query) { this.terminal.print('Search query is required', 'warning'); return; }
- this.terminal.print(`🔍 Searching: ${query}`, 'info');
+ const category = wantImages ? 'images' : (wantVideos ? 'videos' : 'general');
+ const categoryLabel = category === 'images' ? '🖼️' : (category === 'videos' ? '🎬' : '🔍');
- let params = 'q=' + encodeURIComponent(query);
+ this.terminal.print(`${categoryLabel} Searching ${category}: ${query}`, 'info');
+
+ let params = 'q=' + encodeURIComponent(query) + '&categories=' + category;
if (summarize) params += '&summarize=true';
if (fetchContent) params += '&fetch_content=true';
@@ -46,7 +58,7 @@ class SearXNGModule {
return;
}
- this.terminal.print(`\n=== ${results.length} results (via ${data.source || 'unknown'}) ===\n`, 'success');
+ this.terminal.print(`\n=== ${results.length} results (${category}, via ${data.source || 'unknown'}) ===\n`, 'success');
if (data.ai_overview) {
this.terminal.print('🤖 AI Overview:', 'info');
@@ -57,14 +69,35 @@ class SearXNGModule {
results.forEach((r, i) => {
const snippet = r.content || '';
const contentSnippet = r.content_snippet || '';
- this.terminal.printHTML(`
- <div class="search-result">
- <strong>${i + 1}. <a href="${r.url}" target="_blank" style="color: #00aaff;">${r.title || 'No Title'}</a></strong><br/>
- <span style="color: #888888; font-size: 0.85em;">${r.url || 'No URL'}</span><br/>
- <span style="color: #00ff00; font-size: 0.85em;">${(snippet || '').substring(0, 200)}...</span>
- ${contentSnippet ? `<br/><span style="color: #aaaaaa; font-size: 0.8em;">📄 ${contentSnippet.substring(0, 150)}...</span>` : ''}
- </div>
- `);
+ const imgSrc = r.img_src || r.thumbnail_src || '';
+ const videoThumb = r.thumbnail || '';
+
+ if (category === 'images' && imgSrc) {
+ this.terminal.printHTML(`
+ <div class="search-result search-result-image">
+ <a href="${r.url}" target="_blank"><img src="${imgSrc}" style="max-width:200px;max-height:150px;border-radius:4px;object-fit:cover;" alt="${r.title || ''}" loading="lazy"></a>
+ <br/><strong>${i + 1}. <a href="${r.url}" target="_blank" style="color: #00aaff;">${r.title || 'No Title'}</a></strong>
+ </div>
+ `);
+ } else if (category === 'videos' && videoThumb) {
+ this.terminal.printHTML(`
+ <div class="search-result search-result-video">
+ <a href="${r.url}" target="_blank"><img src="${videoThumb}" style="max-width:200px;max-height:150px;border-radius:4px;object-fit:cover;" alt="${r.title || ''}" loading="lazy"></a>
+ <br/><strong>${i + 1}. <a href="${r.url}" target="_blank" style="color: #00aaff;">${r.title || 'No Title'}</a></strong><br/>
+ <span style="color: #888888; font-size: 0.85em;">${r.url || ''}</span>
+ </div>
+ `);
+ } else {
+ this.terminal.printHTML(`
+ <div class="search-result">
+ <strong>${i + 1}. <a href="${r.url}" target="_blank" style="color: #00aaff;">${r.title || 'No Title'}</a></strong><br/>
+ <span style="color: #888888; font-size: 0.85em;">${r.url || 'No URL'}</span><br/>
+ <span style="color: #00ff00; font-size: 0.85em;">${(snippet || '').substring(0, 200)}...</span>
+ ${contentSnippet ? `<br/><span style="color: #aaaaaa; font-size: 0.8em;">📄 ${contentSnippet.substring(0, 150)}...</span>` : ''}
+ ${imgSrc && category === 'general' ? `<br/><a href="${imgSrc}" target="_blank" style="color: #ff8800; font-size: 0.8em;">🖼️ View image</a>` : ''}
+ </div>
+ `);
+ }
});
} catch (e) {
this.terminal.print('Search failed: ' + e.message, 'error');