mirror of
https://github.com/outbackdingo/optimclaw.git
synced 2026-08-25 14:53:34 +00:00
web: add integrity check for marked CDN and cap highlight regex input (#109)
* web: add integrity check for marked CDN and cap highlight regex input * web: normalize memory search query before snippet+highlight matching * web: place memory query length constant with top-level config --------- Co-authored-by: Clawyered <[email protected]> Co-authored-by: Illia Polosukhin <[email protected]>
This commit is contained in:
co-authored by
Clawyered
Illia Polosukhin
parent
956037c4d3
commit
d04af5cd75
@@ -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 = '<div class="path">' + escapeHtml(result.path) + '</div>'
|
||||
+ '<div class="snippet">' + highlightQuery(snippet, query) + '</div>';
|
||||
+ '<div class="snippet">' + highlightQuery(snippet, normalizedQuery) + '</div>';
|
||||
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, '<mark>$1</mark>');
|
||||
}
|
||||
|
||||
// --- Logs ---
|
||||
|
||||
const LOG_MAX_ENTRIES = 2000;
|
||||
|
||||
@@ -5,7 +5,11 @@
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>IronClaw</title>
|
||||
<link rel="stylesheet" href="/style.css">
|
||||
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
|
||||
<script
|
||||
src="https://cdn.jsdelivr.net/npm/[email protected]/lib/marked.umd.min.js"
|
||||
integrity="sha384-pN9zSKOnTZwXRtYZAu0PBPEgR2B7DOC1aeLxQ33oJ0oy5iN1we6gm57xldM2irDG"
|
||||
crossorigin="anonymous"
|
||||
></script>
|
||||
</head>
|
||||
<body>
|
||||
<!-- Auth Screen -->
|
||||
|
||||
Reference in New Issue
Block a user