clay-server 2.14.0 → 2.15.0-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +25 -40
- package/lib/mates.js +159 -0
- package/lib/project.js +93 -8
- package/lib/public/app.js +5 -11
- package/lib/public/css/input.css +9 -1
- package/lib/public/css/mates.css +167 -13
- package/lib/public/css/session-search.css +9 -0
- package/lib/public/css/sidebar.css +0 -17
- package/lib/public/index.html +15 -1
- package/lib/public/modules/command-palette.js +47 -100
- package/lib/public/modules/mate-knowledge.js +375 -121
- package/lib/public/modules/mate-sidebar.js +2 -6
- package/lib/public/modules/sidebar.js +0 -196
- package/lib/sessions.js +1 -1
- package/package.json +1 -1
|
@@ -468,7 +468,6 @@ function renderSessionItem(s) {
|
|
|
468
468
|
dismissOverlayPanels();
|
|
469
469
|
closeSidebar();
|
|
470
470
|
if (pendingQuery) {
|
|
471
|
-
closeSearch();
|
|
472
471
|
setTimeout(function () { openSessionSearch(pendingQuery); }, 400);
|
|
473
472
|
}
|
|
474
473
|
}
|
|
@@ -556,17 +555,6 @@ export function handleSearchResults(msg) {
|
|
|
556
555
|
}
|
|
557
556
|
searchMatchIds = ids;
|
|
558
557
|
renderSessionList(null);
|
|
559
|
-
|
|
560
|
-
// Build timeline for current session if it matches
|
|
561
|
-
var activeEl = ctx.sessionListEl.querySelector(".session-item.active");
|
|
562
|
-
if (activeEl) {
|
|
563
|
-
var activeId = parseInt(activeEl.dataset.sessionId, 10);
|
|
564
|
-
if (ids.has(activeId)) {
|
|
565
|
-
buildSearchTimeline(searchQuery);
|
|
566
|
-
} else {
|
|
567
|
-
removeSearchTimeline();
|
|
568
|
-
}
|
|
569
|
-
}
|
|
570
558
|
}
|
|
571
559
|
|
|
572
560
|
export function updateSessionPresence(presence) {
|
|
@@ -878,7 +866,6 @@ export function initSidebar(_ctx) {
|
|
|
878
866
|
searchQuery = "";
|
|
879
867
|
searchMatchIds = null;
|
|
880
868
|
if (searchDebounce) { clearTimeout(searchDebounce); searchDebounce = null; }
|
|
881
|
-
removeSearchTimeline();
|
|
882
869
|
renderSessionList(null);
|
|
883
870
|
}
|
|
884
871
|
|
|
@@ -901,7 +888,6 @@ export function initSidebar(_ctx) {
|
|
|
901
888
|
if (searchDebounce) clearTimeout(searchDebounce);
|
|
902
889
|
if (!searchQuery) {
|
|
903
890
|
searchMatchIds = null;
|
|
904
|
-
removeSearchTimeline();
|
|
905
891
|
renderSessionList(null);
|
|
906
892
|
return;
|
|
907
893
|
}
|
|
@@ -1276,188 +1262,6 @@ export function populateCliSessionList(sessions) {
|
|
|
1276
1262
|
}
|
|
1277
1263
|
}
|
|
1278
1264
|
|
|
1279
|
-
// --- Search hit timeline (right-side markers) ---
|
|
1280
|
-
var searchTimelineScrollHandler = null;
|
|
1281
|
-
var activeSearchQuery = ""; // query active in the timeline
|
|
1282
|
-
var pendingSearchScrollTarget = null; // { historyIndex, snippet, query } for scroll after history load
|
|
1283
|
-
|
|
1284
|
-
export function getActiveSearchQuery() {
|
|
1285
|
-
return searchQuery;
|
|
1286
|
-
}
|
|
1287
|
-
|
|
1288
|
-
// Request server-side content search for the active session
|
|
1289
|
-
export function buildSearchTimeline(query) {
|
|
1290
|
-
removeSearchTimeline();
|
|
1291
|
-
if (!query) return;
|
|
1292
|
-
activeSearchQuery = query;
|
|
1293
|
-
// Request full-history search from server
|
|
1294
|
-
if (ctx.ws && ctx.connected) {
|
|
1295
|
-
ctx.ws.send(JSON.stringify({ type: "search_session_content", query: query }));
|
|
1296
|
-
}
|
|
1297
|
-
}
|
|
1298
|
-
|
|
1299
|
-
// Handle server response with full-history search results
|
|
1300
|
-
export function handleSearchContentResults(msg) {
|
|
1301
|
-
if (msg.query !== activeSearchQuery) return; // stale response
|
|
1302
|
-
var savedQuery = activeSearchQuery;
|
|
1303
|
-
removeSearchTimeline();
|
|
1304
|
-
if (!msg.hits || msg.hits.length === 0) return;
|
|
1305
|
-
activeSearchQuery = savedQuery;
|
|
1306
|
-
|
|
1307
|
-
var hits = msg.hits;
|
|
1308
|
-
var total = msg.total;
|
|
1309
|
-
var messagesEl = ctx.messagesEl;
|
|
1310
|
-
|
|
1311
|
-
var timeline = document.createElement("div");
|
|
1312
|
-
timeline.className = "search-timeline";
|
|
1313
|
-
timeline.id = "search-timeline";
|
|
1314
|
-
|
|
1315
|
-
var track = document.createElement("div");
|
|
1316
|
-
track.className = "rewind-timeline-track";
|
|
1317
|
-
track.dataset.historyTotal = total;
|
|
1318
|
-
|
|
1319
|
-
var viewport = document.createElement("div");
|
|
1320
|
-
viewport.className = "rewind-timeline-viewport";
|
|
1321
|
-
track.appendChild(viewport);
|
|
1322
|
-
|
|
1323
|
-
for (var i = 0; i < hits.length; i++) {
|
|
1324
|
-
var hit = hits[i];
|
|
1325
|
-
// Position based on historyIndex relative to total history length
|
|
1326
|
-
var pct = total <= 1 ? 50 : 6 + (hit.historyIndex / (total - 1)) * 88;
|
|
1327
|
-
|
|
1328
|
-
var snippetText = hit.snippet;
|
|
1329
|
-
if (snippetText.length > 24) snippetText = snippetText.substring(0, 24) + "\u2026";
|
|
1330
|
-
|
|
1331
|
-
var marker = document.createElement("div");
|
|
1332
|
-
marker.className = "rewind-timeline-marker search-hit-marker";
|
|
1333
|
-
marker.innerHTML = iconHtml("search") + '<span class="marker-text">' + escapeHtml(snippetText) + '</span>';
|
|
1334
|
-
marker.style.top = pct + "%";
|
|
1335
|
-
// Store historyIndex for click handling and viewport check
|
|
1336
|
-
marker.dataset.historyIndex = hit.historyIndex;
|
|
1337
|
-
|
|
1338
|
-
(function(hitData, markerEl) {
|
|
1339
|
-
markerEl.addEventListener("click", function() {
|
|
1340
|
-
scrollToSearchHit(hitData.historyIndex, hitData.snippet, msg.query);
|
|
1341
|
-
});
|
|
1342
|
-
})(hit, marker);
|
|
1343
|
-
|
|
1344
|
-
track.appendChild(marker);
|
|
1345
|
-
}
|
|
1346
|
-
|
|
1347
|
-
timeline.appendChild(track);
|
|
1348
|
-
|
|
1349
|
-
// Position to align with messages area
|
|
1350
|
-
var appEl = ctx.$("app");
|
|
1351
|
-
var titleBarEl = document.querySelector(".title-bar-content");
|
|
1352
|
-
var inputAreaEl = ctx.$("input-area");
|
|
1353
|
-
var appRect = appEl.getBoundingClientRect();
|
|
1354
|
-
var titleBarRect = titleBarEl ? titleBarEl.getBoundingClientRect() : { bottom: appRect.top };
|
|
1355
|
-
var inputRect = inputAreaEl.getBoundingClientRect();
|
|
1356
|
-
|
|
1357
|
-
timeline.style.top = (titleBarRect.bottom - appRect.top + 4) + "px";
|
|
1358
|
-
timeline.style.bottom = (appRect.bottom - inputRect.top + 4) + "px";
|
|
1359
|
-
|
|
1360
|
-
appEl.appendChild(timeline);
|
|
1361
|
-
refreshIcons();
|
|
1362
|
-
|
|
1363
|
-
searchTimelineScrollHandler = function() { updateSearchTimelineViewport(track, viewport); };
|
|
1364
|
-
messagesEl.addEventListener("scroll", searchTimelineScrollHandler);
|
|
1365
|
-
updateSearchTimelineViewport(track, viewport);
|
|
1366
|
-
}
|
|
1367
|
-
|
|
1368
|
-
function scrollToSearchHit(historyIndex, snippet, query) {
|
|
1369
|
-
var historyFrom = ctx.getHistoryFrom ? ctx.getHistoryFrom() : 0;
|
|
1370
|
-
if (historyIndex < historyFrom) {
|
|
1371
|
-
// Need to load older history first
|
|
1372
|
-
pendingSearchScrollTarget = { historyIndex: historyIndex, snippet: snippet, query: query };
|
|
1373
|
-
if (ctx.ws && ctx.connected) {
|
|
1374
|
-
ctx.ws.send(JSON.stringify({ type: "load_more_history", before: historyFrom, target: historyIndex }));
|
|
1375
|
-
}
|
|
1376
|
-
return;
|
|
1377
|
-
}
|
|
1378
|
-
// History is loaded, find matching element in DOM
|
|
1379
|
-
findAndScrollToMatch(snippet, query);
|
|
1380
|
-
}
|
|
1381
|
-
|
|
1382
|
-
function findAndScrollToMatch(snippet, query) {
|
|
1383
|
-
var messagesEl = ctx.messagesEl;
|
|
1384
|
-
var q = query.toLowerCase();
|
|
1385
|
-
var allMsgs = messagesEl.querySelectorAll(".msg-user, .msg-assistant");
|
|
1386
|
-
for (var i = 0; i < allMsgs.length; i++) {
|
|
1387
|
-
var msgEl = allMsgs[i];
|
|
1388
|
-
var textEl = msgEl.querySelector(".bubble") || msgEl.querySelector(".md-content");
|
|
1389
|
-
if (!textEl) continue;
|
|
1390
|
-
var text = textEl.textContent || "";
|
|
1391
|
-
if (text.toLowerCase().indexOf(q) === -1) continue;
|
|
1392
|
-
// Check if the snippet content matches (strip ellipsis for comparison)
|
|
1393
|
-
var cleanSnippet = snippet.replace(/^\u2026/, "").replace(/\u2026$/, "");
|
|
1394
|
-
if (text.indexOf(cleanSnippet) !== -1) {
|
|
1395
|
-
msgEl.scrollIntoView({ behavior: "smooth", block: "center" });
|
|
1396
|
-
msgEl.classList.remove("search-blink");
|
|
1397
|
-
void msgEl.offsetWidth;
|
|
1398
|
-
msgEl.classList.add("search-blink");
|
|
1399
|
-
return;
|
|
1400
|
-
}
|
|
1401
|
-
}
|
|
1402
|
-
// Fallback: scroll to any element containing the query text
|
|
1403
|
-
for (var j = 0; j < allMsgs.length; j++) {
|
|
1404
|
-
var el = allMsgs[j];
|
|
1405
|
-
var tEl = el.querySelector(".bubble") || el.querySelector(".md-content");
|
|
1406
|
-
if (!tEl) continue;
|
|
1407
|
-
if ((tEl.textContent || "").toLowerCase().indexOf(q) !== -1) {
|
|
1408
|
-
el.scrollIntoView({ behavior: "smooth", block: "center" });
|
|
1409
|
-
el.classList.remove("search-blink");
|
|
1410
|
-
void el.offsetWidth;
|
|
1411
|
-
el.classList.add("search-blink");
|
|
1412
|
-
return;
|
|
1413
|
-
}
|
|
1414
|
-
}
|
|
1415
|
-
}
|
|
1416
|
-
|
|
1417
|
-
// Called after history_prepend completes, to scroll to pending target
|
|
1418
|
-
export function onHistoryPrepended() {
|
|
1419
|
-
if (!pendingSearchScrollTarget) return;
|
|
1420
|
-
var target = pendingSearchScrollTarget;
|
|
1421
|
-
pendingSearchScrollTarget = null;
|
|
1422
|
-
requestAnimationFrame(function() {
|
|
1423
|
-
findAndScrollToMatch(target.snippet, target.query);
|
|
1424
|
-
});
|
|
1425
|
-
}
|
|
1426
|
-
|
|
1427
|
-
function updateSearchTimelineViewport(track, viewport) {
|
|
1428
|
-
if (!track) return;
|
|
1429
|
-
var messagesEl = ctx.messagesEl;
|
|
1430
|
-
var scrollH = messagesEl.scrollHeight;
|
|
1431
|
-
var viewH = messagesEl.clientHeight;
|
|
1432
|
-
|
|
1433
|
-
// Map the visible scroll area to the timeline range (6% to 94%)
|
|
1434
|
-
var historyFrom = ctx.getHistoryFrom ? ctx.getHistoryFrom() : 0;
|
|
1435
|
-
var total = parseInt(track.dataset.historyTotal || "0", 10) || 1;
|
|
1436
|
-
var timelineStart = 6 + (historyFrom / (total - 1 || 1)) * 88;
|
|
1437
|
-
var timelineEnd = 94;
|
|
1438
|
-
var timelineRange = timelineEnd - timelineStart;
|
|
1439
|
-
|
|
1440
|
-
if (scrollH <= viewH) {
|
|
1441
|
-
viewport.style.top = timelineStart + "%";
|
|
1442
|
-
viewport.style.height = timelineRange + "%";
|
|
1443
|
-
} else {
|
|
1444
|
-
var scrollFrac = messagesEl.scrollTop / scrollH;
|
|
1445
|
-
var viewFrac = viewH / scrollH;
|
|
1446
|
-
viewport.style.top = (timelineStart + scrollFrac * timelineRange) + "%";
|
|
1447
|
-
viewport.style.height = (viewFrac * timelineRange) + "%";
|
|
1448
|
-
}
|
|
1449
|
-
}
|
|
1450
|
-
|
|
1451
|
-
export function removeSearchTimeline() {
|
|
1452
|
-
var existing = document.getElementById("search-timeline");
|
|
1453
|
-
if (existing) existing.remove();
|
|
1454
|
-
if (searchTimelineScrollHandler && ctx.messagesEl) {
|
|
1455
|
-
ctx.messagesEl.removeEventListener("scroll", searchTimelineScrollHandler);
|
|
1456
|
-
searchTimelineScrollHandler = null;
|
|
1457
|
-
}
|
|
1458
|
-
activeSearchQuery = "";
|
|
1459
|
-
}
|
|
1460
|
-
|
|
1461
1265
|
// --- Icon Strip (Discord-style project icons) ---
|
|
1462
1266
|
var iconStripTooltip = null;
|
|
1463
1267
|
|
package/lib/sessions.js
CHANGED
|
@@ -310,7 +310,7 @@ function createSessionManager(opts) {
|
|
|
310
310
|
|
|
311
311
|
function findTurnBoundary(history, targetIndex) {
|
|
312
312
|
for (var i = targetIndex; i >= 0; i--) {
|
|
313
|
-
if (history[i].type === "user_message") return i;
|
|
313
|
+
if (history[i] && history[i].type === "user_message") return i;
|
|
314
314
|
}
|
|
315
315
|
return 0;
|
|
316
316
|
}
|