diff --git a/src/channels/web/static/app.js b/src/channels/web/static/app.js
index cdc1e06d..398f2f54 100644
--- a/src/channels/web/static/app.js
+++ b/src/channels/web/static/app.js
@@ -12,6 +12,7 @@ let loadingOlder = false;
let jobEvents = new Map(); // job_id -> Array of events
let jobListRefreshTimer = null;
const JOB_EVENTS_CAP = 500;
+const MEMORY_SEARCH_QUERY_MAX_LENGTH = 100;
// --- Auth ---
@@ -1001,9 +1002,12 @@ function buildBreadcrumb(path) {
}
function searchMemory(query) {
+ const normalizedQuery = normalizeSearchQuery(query);
+ if (!normalizedQuery) return;
+
apiFetch('/api/memory/search', {
method: 'POST',
- body: { query, limit: 20 },
+ body: { query: normalizedQuery, limit: 20 },
}).then((data) => {
const tree = document.getElementById('memory-tree');
tree.innerHTML = '';
@@ -1014,18 +1018,23 @@ function searchMemory(query) {
for (const result of data.results) {
const item = document.createElement('div');
item.className = 'search-result';
- const snippet = snippetAround(result.content, query, 120);
+ const snippet = snippetAround(result.content, normalizedQuery, 120);
item.innerHTML = '
' + escapeHtml(result.path) + '
'
- + '' + highlightQuery(snippet, query) + '
';
+ + '' + highlightQuery(snippet, normalizedQuery) + '
';
item.addEventListener('click', () => readMemoryFile(result.path));
tree.appendChild(item);
}
}).catch(() => {});
}
+function normalizeSearchQuery(query) {
+ return (typeof query === 'string' ? query : '').slice(0, MEMORY_SEARCH_QUERY_MAX_LENGTH);
+}
+
function snippetAround(text, query, len) {
+ const normalizedQuery = normalizeSearchQuery(query);
const lower = text.toLowerCase();
- const idx = lower.indexOf(query.toLowerCase());
+ const idx = lower.indexOf(normalizedQuery.toLowerCase());
if (idx < 0) return text.substring(0, len);
const start = Math.max(0, idx - Math.floor(len / 2));
const end = Math.min(text.length, start + len);
@@ -1038,11 +1047,11 @@ function snippetAround(text, query, len) {
function highlightQuery(text, query) {
if (!query) return escapeHtml(text);
const escaped = escapeHtml(text);
- const queryEscaped = query.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
+ const normalizedQuery = normalizeSearchQuery(query);
+ const queryEscaped = normalizedQuery.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
const re = new RegExp('(' + queryEscaped + ')', 'gi');
return escaped.replace(re, '$1');
}
-
// --- Logs ---
const LOG_MAX_ENTRIES = 2000;
diff --git a/src/channels/web/static/index.html b/src/channels/web/static/index.html
index bf6c227c..ddcf6892 100644
--- a/src/channels/web/static/index.html
+++ b/src/channels/web/static/index.html
@@ -5,7 +5,11 @@
IronClaw
-
+