sapper-iq 1.2.4 → 1.3.0
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/package.json +1 -1
- package/sapper-ui.mjs +186 -0
- package/sapper.mjs +24 -24
package/package.json
CHANGED
package/sapper-ui.mjs
CHANGED
|
@@ -285,6 +285,47 @@ function buildHTML() {
|
|
|
285
285
|
font-style: italic; font-size: 11px; white-space: pre-wrap; word-break: break-word; }
|
|
286
286
|
#activityPanel .note:before { content: '💬 '; margin-right: 2px; font-style: normal; }
|
|
287
287
|
#activityPanel .empty { padding: 12px; color: var(--dim); text-align: center; font-size: 11px; }
|
|
288
|
+
|
|
289
|
+
/* Index tray — multi-select files/folders to send into chat */
|
|
290
|
+
#indexPanel { display: none; border-bottom: 1px solid var(--border);
|
|
291
|
+
background: linear-gradient(180deg, rgba(88,166,255,.08), rgba(88,166,255,.02));
|
|
292
|
+
font-family: ui-monospace, 'SF Mono', monospace; font-size: 11px; }
|
|
293
|
+
#indexPanel.on { display: block; }
|
|
294
|
+
#indexPanel .ih { display: flex; align-items: center; gap: 6px; padding: 5px 10px;
|
|
295
|
+
border-bottom: 1px solid var(--border); color: var(--accent); font-size: 10px;
|
|
296
|
+
text-transform: uppercase; letter-spacing: .5px; }
|
|
297
|
+
#indexPanel .ih .icnt { color: var(--muted); text-transform: none; letter-spacing: 0; }
|
|
298
|
+
#indexPanel .ih .iact { margin-left: auto; display: inline-flex; gap: 4px; }
|
|
299
|
+
#indexPanel .ih .iact button { background: transparent; color: var(--accent);
|
|
300
|
+
border: 1px solid var(--border2); border-radius: 3px; padding: 1px 7px; font-size: 10px;
|
|
301
|
+
cursor: pointer; font-family: inherit; line-height: 1.3; }
|
|
302
|
+
#indexPanel .ih .iact button:hover { border-color: var(--accent); }
|
|
303
|
+
#indexPanel .ih .iact button.primary { color: #fff; background: var(--accent); border-color: var(--accent); }
|
|
304
|
+
#indexPanel .ih .iact button.primary:hover { background: var(--accent2); border-color: var(--accent2); }
|
|
305
|
+
#indexPanel .ih .iact button.danger { color: var(--muted); }
|
|
306
|
+
#indexPanel .ih .iact button.danger:hover { color: var(--red); border-color: var(--red); }
|
|
307
|
+
#indexPanel .chips { display: flex; flex-wrap: wrap; gap: 4px; padding: 6px 10px 4px;
|
|
308
|
+
max-height: 110px; overflow-y: auto; }
|
|
309
|
+
#indexPanel .chip { display: inline-flex; align-items: center; gap: 4px;
|
|
310
|
+
background: rgba(88,166,255,.12); border: 1px solid rgba(88,166,255,.3);
|
|
311
|
+
border-radius: 10px; padding: 1px 4px 1px 8px; font-size: 10px; color: var(--fg); }
|
|
312
|
+
#indexPanel .chip.dir { background: rgba(210,153,34,.12); border-color: rgba(210,153,34,.3); }
|
|
313
|
+
#indexPanel .chip .cp { max-width: 160px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
314
|
+
#indexPanel .chip .cx { cursor: pointer; opacity: .55; padding: 0 3px; font-size: 12px; }
|
|
315
|
+
#indexPanel .chip .cx:hover { opacity: 1; color: var(--red); }
|
|
316
|
+
#indexPanel .empty { padding: 8px 10px; color: var(--dim); font-style: italic; }
|
|
317
|
+
#indexPanel .icmt { display: block; margin: 4px 10px 8px; width: calc(100% - 20px);
|
|
318
|
+
box-sizing: border-box; background: var(--panel); color: var(--fg);
|
|
319
|
+
border: 1px solid var(--border2); border-radius: 4px; padding: 4px 6px;
|
|
320
|
+
font-size: 11px; font-family: inherit; resize: vertical; min-height: 26px; max-height: 80px; }
|
|
321
|
+
#indexPanel .icmt:focus { outline: none; border-color: var(--accent); }
|
|
322
|
+
/* Per-row index checkbox (visible only when index mode is on) */
|
|
323
|
+
.row .chk { display: none; width: 12px; flex-shrink: 0; color: var(--dim);
|
|
324
|
+
text-align: center; font-size: 11px; line-height: 1; }
|
|
325
|
+
body.indexmode .row .chk { display: inline-block; cursor: pointer; }
|
|
326
|
+
body.indexmode .row .chk:hover { color: var(--accent); }
|
|
327
|
+
.row .chk.on { color: var(--accent); }
|
|
328
|
+
.ftb.on { color: var(--accent); }
|
|
288
329
|
.tree { font-family: ui-monospace, 'SF Mono', monospace; font-size: 12px; padding-bottom: 12px; }
|
|
289
330
|
.row { display: flex; align-items: center; gap: 4px; padding: 3px 8px; cursor: pointer; color: var(--muted);
|
|
290
331
|
white-space: nowrap; user-select: none; position: relative; }
|
|
@@ -592,6 +633,7 @@ function buildHTML() {
|
|
|
592
633
|
<button class="ftb" title="New file" onclick="newItemPrompt('file','')">🗎<sup>+</sup></button>
|
|
593
634
|
<button class="ftb" title="New folder" onclick="newItemPrompt('folder','')">📁<sup>+</sup></button>
|
|
594
635
|
<button class="ftb" id="ftbAct" title="Show activity log" onclick="toggleActivity()">☉</button>
|
|
636
|
+
<button class="ftb" id="ftbIdx" title="Index files/folders into chat (multi-select)" onclick="toggleIndexMode()">📚</button>
|
|
595
637
|
<span class="ftb-spacer"></span>
|
|
596
638
|
<button class="ftb" title="Clear change marks" onclick="clearAllMarks()">✕</button>
|
|
597
639
|
<button class="ftb" title="Refresh tree" onclick="loadTree()">↺</button>
|
|
@@ -601,6 +643,17 @@ function buildHTML() {
|
|
|
601
643
|
<div class="ah">Recent activity<span class="acl" onclick="clearActivity()">clear</span></div>
|
|
602
644
|
<div id="activityList"></div>
|
|
603
645
|
</div>
|
|
646
|
+
<div id="indexPanel">
|
|
647
|
+
<div class="ih">
|
|
648
|
+
<span>Index</span><span class="icnt" id="idxCount">0 items</span>
|
|
649
|
+
<span class="iact">
|
|
650
|
+
<button class="primary" title="Send to chat (Enter sends if a prompt is filled, otherwise files are staged at the cursor)" onclick="sendIndexToChat()">Send</button>
|
|
651
|
+
<button class="danger" title="Clear all" onclick="clearIndex()">Clear</button>
|
|
652
|
+
</span>
|
|
653
|
+
</div>
|
|
654
|
+
<div class="chips" id="idxChips"></div>
|
|
655
|
+
<textarea class="icmt" id="idxComment" placeholder="Optional prompt — fill this to send immediately. Empty = stage at cursor so you can keep typing."></textarea>
|
|
656
|
+
</div>
|
|
604
657
|
<div class="tree" id="tree"></div>
|
|
605
658
|
</div>
|
|
606
659
|
<div class="pane" id="pane-config">
|
|
@@ -721,6 +774,8 @@ var state = {
|
|
|
721
774
|
marks: {}, // path -> { kind, count, ts }
|
|
722
775
|
activity: [], // ordered list of {kind, path, isDir, ts}
|
|
723
776
|
activityOpen: false,
|
|
777
|
+
indexMode: false, // true = show checkboxes on tree rows
|
|
778
|
+
indexSet: {}, // path -> { isDir, ts } selected for "Index to chat"
|
|
724
779
|
};
|
|
725
780
|
|
|
726
781
|
var cm = null; // CodeMirror instance (lazy)
|
|
@@ -1172,7 +1227,10 @@ function renderEntries(container, basePath, entries, depth) {
|
|
|
1172
1227
|
row.dataset.isdir = entry.isDir ? '1' : '0';
|
|
1173
1228
|
row.style.paddingLeft = (8 + depth * 14) + 'px';
|
|
1174
1229
|
var chev = entry.isDir ? (state.expanded[path] ? '▾' : '▸') : '';
|
|
1230
|
+
var chkOn = state.indexSet[path] ? ' on' : '';
|
|
1231
|
+
var chkChar = state.indexSet[path] ? '☑' : '☐'; // ☑ / ☐
|
|
1175
1232
|
row.innerHTML =
|
|
1233
|
+
'<span class="chk' + chkOn + '" title="Add to index">' + chkChar + '</span>' +
|
|
1176
1234
|
'<span class="chev">' + chev + '</span>' +
|
|
1177
1235
|
'<span class="ico">' + fileIcon(entry.name, entry.isDir) + '</span>' +
|
|
1178
1236
|
'<span class="name">' + esc(entry.name) + '</span>' +
|
|
@@ -1181,6 +1239,11 @@ function renderEntries(container, basePath, entries, depth) {
|
|
|
1181
1239
|
'<span class="badge">●</span>' +
|
|
1182
1240
|
'<span class="rmenu" title="Options">⋯</span>';
|
|
1183
1241
|
row.addEventListener('click', function(ev){
|
|
1242
|
+
if (ev.target && ev.target.classList && ev.target.classList.contains('chk')) {
|
|
1243
|
+
ev.stopPropagation();
|
|
1244
|
+
toggleIndex(path, entry.isDir);
|
|
1245
|
+
return;
|
|
1246
|
+
}
|
|
1184
1247
|
if (ev.target && ev.target.classList && ev.target.classList.contains('rmenu')) {
|
|
1185
1248
|
ev.stopPropagation();
|
|
1186
1249
|
openRowMenu(ev.target, path, entry.isDir);
|
|
@@ -1281,6 +1344,12 @@ function openRowMenu(anchor, path, isDir) {
|
|
|
1281
1344
|
items.push({ label: 'Copy path', fn: function(){ copyText(path); showToast('Path copied'); } });
|
|
1282
1345
|
items.push({ label: 'Copy name', fn: function(){ copyText(path.split('/').pop()); showToast('Name copied'); } });
|
|
1283
1346
|
items.push({ sep: true });
|
|
1347
|
+
var inIdx = !!state.indexSet[path];
|
|
1348
|
+
items.push({
|
|
1349
|
+
label: (inIdx ? '📚 Remove from index' : '📚 Add to index'),
|
|
1350
|
+
fn: function(){ toggleIndex(path, isDir); if (!state.indexMode) toggleIndexMode(true); }
|
|
1351
|
+
});
|
|
1352
|
+
items.push({ sep: true });
|
|
1284
1353
|
items.push({ label: 'Reveal in Finder', fn: function(){
|
|
1285
1354
|
fetch('/api/fs/reveal', { method: 'POST', headers:{'Content-Type':'application/json'}, body: JSON.stringify({ path: path }) });
|
|
1286
1355
|
}});
|
|
@@ -1748,6 +1817,123 @@ function sendPasteToTerm(text) {
|
|
|
1748
1817
|
return true;
|
|
1749
1818
|
}
|
|
1750
1819
|
|
|
1820
|
+
function sendRawToTerm(text) {
|
|
1821
|
+
if (!ws || ws.readyState !== 1) {
|
|
1822
|
+
showToast('Terminal not connected', 'err');
|
|
1823
|
+
return false;
|
|
1824
|
+
}
|
|
1825
|
+
ws.send(text);
|
|
1826
|
+
return true;
|
|
1827
|
+
}
|
|
1828
|
+
|
|
1829
|
+
// ─── Index tray: multi-select files/folders into the chat ────────
|
|
1830
|
+
try { state.indexSet = JSON.parse(localStorage.getItem('sapperIndex') || '{}') || {}; } catch(e) { state.indexSet = {}; }
|
|
1831
|
+
function saveIndex() {
|
|
1832
|
+
try { localStorage.setItem('sapperIndex', JSON.stringify(state.indexSet)); } catch(e) {}
|
|
1833
|
+
}
|
|
1834
|
+
|
|
1835
|
+
window.toggleIndexMode = function(forceOn) {
|
|
1836
|
+
state.indexMode = (forceOn === true) ? true : !state.indexMode;
|
|
1837
|
+
document.body.classList.toggle('indexmode', state.indexMode);
|
|
1838
|
+
var btn = document.getElementById('ftbIdx');
|
|
1839
|
+
if (btn) btn.classList.toggle('on', state.indexMode);
|
|
1840
|
+
var panel = document.getElementById('indexPanel');
|
|
1841
|
+
if (panel) panel.classList.toggle('on', state.indexMode);
|
|
1842
|
+
if (state.indexMode) renderIndex();
|
|
1843
|
+
};
|
|
1844
|
+
|
|
1845
|
+
window.toggleIndex = function(path, isDir) {
|
|
1846
|
+
if (state.indexSet[path]) {
|
|
1847
|
+
delete state.indexSet[path];
|
|
1848
|
+
} else {
|
|
1849
|
+
state.indexSet[path] = { isDir: !!isDir, ts: Date.now() };
|
|
1850
|
+
}
|
|
1851
|
+
saveIndex();
|
|
1852
|
+
// Update the row checkbox without full rerender
|
|
1853
|
+
var row = document.querySelector('.row[data-path="' + cssEscape(path) + '"]');
|
|
1854
|
+
if (row) {
|
|
1855
|
+
var chk = row.querySelector('.chk');
|
|
1856
|
+
if (chk) {
|
|
1857
|
+
var on = !!state.indexSet[path];
|
|
1858
|
+
chk.classList.toggle('on', on);
|
|
1859
|
+
chk.innerHTML = on ? '☑' : '☐';
|
|
1860
|
+
}
|
|
1861
|
+
}
|
|
1862
|
+
renderIndex();
|
|
1863
|
+
};
|
|
1864
|
+
|
|
1865
|
+
window.clearIndex = function() {
|
|
1866
|
+
state.indexSet = {};
|
|
1867
|
+
saveIndex();
|
|
1868
|
+
document.querySelectorAll('.row .chk.on').forEach(function(el){
|
|
1869
|
+
el.classList.remove('on'); el.innerHTML = '☐';
|
|
1870
|
+
});
|
|
1871
|
+
renderIndex();
|
|
1872
|
+
showToast('Index cleared');
|
|
1873
|
+
};
|
|
1874
|
+
|
|
1875
|
+
function renderIndex() {
|
|
1876
|
+
var panel = document.getElementById('indexPanel');
|
|
1877
|
+
if (!panel) return;
|
|
1878
|
+
var chips = document.getElementById('idxChips');
|
|
1879
|
+
var count = document.getElementById('idxCount');
|
|
1880
|
+
var paths = Object.keys(state.indexSet).sort();
|
|
1881
|
+
if (count) count.textContent = paths.length + ' item' + (paths.length === 1 ? '' : 's');
|
|
1882
|
+
if (!chips) return;
|
|
1883
|
+
if (!paths.length) {
|
|
1884
|
+
chips.innerHTML = '<div class="empty">Tick files or folders in the tree, or right-click > Add to index.</div>';
|
|
1885
|
+
return;
|
|
1886
|
+
}
|
|
1887
|
+
chips.innerHTML = paths.map(function(p){
|
|
1888
|
+
var info = state.indexSet[p];
|
|
1889
|
+
var cls = info.isDir ? 'chip dir' : 'chip';
|
|
1890
|
+
var ico = info.isDir ? '📁' : '🗎';
|
|
1891
|
+
return '<span class="' + cls + '" title="' + esc(p) + '">' +
|
|
1892
|
+
'<span>' + ico + '</span>' +
|
|
1893
|
+
'<span class="cp">' + esc(p) + '</span>' +
|
|
1894
|
+
'<span class="cx" data-p="' + esc(p) + '" title="Remove">×</span>' +
|
|
1895
|
+
'</span>';
|
|
1896
|
+
}).join('');
|
|
1897
|
+
chips.querySelectorAll('.cx').forEach(function(el){
|
|
1898
|
+
el.addEventListener('click', function(ev){
|
|
1899
|
+
ev.stopPropagation();
|
|
1900
|
+
toggleIndex(el.getAttribute('data-p'), state.indexSet[el.getAttribute('data-p')] && state.indexSet[el.getAttribute('data-p')].isDir);
|
|
1901
|
+
});
|
|
1902
|
+
});
|
|
1903
|
+
}
|
|
1904
|
+
|
|
1905
|
+
window.sendIndexToChat = function() {
|
|
1906
|
+
var paths = Object.keys(state.indexSet);
|
|
1907
|
+
if (!paths.length) { showToast('Index is empty', 'err'); return; }
|
|
1908
|
+
if (!ws || ws.readyState !== 1) { showToast('Terminal not connected', 'err'); return; }
|
|
1909
|
+
var files = [], dirs = [];
|
|
1910
|
+
paths.forEach(function(p){
|
|
1911
|
+
if (state.indexSet[p] && state.indexSet[p].isDir) dirs.push(p); else files.push(p);
|
|
1912
|
+
});
|
|
1913
|
+
// 1) /scan each folder (each sent as its own command + Enter)
|
|
1914
|
+
dirs.forEach(function(d){ sendPasteToTerm('/scan ' + d); });
|
|
1915
|
+
// 2) Build attachments token for files
|
|
1916
|
+
var atTokens = files.map(function(f){ return '@' + f; }).join(' ');
|
|
1917
|
+
var comment = (document.getElementById('idxComment') || {}).value || '';
|
|
1918
|
+
comment = comment.trim();
|
|
1919
|
+
if (comment) {
|
|
1920
|
+
// Send a complete message that Sapper will execute immediately
|
|
1921
|
+
var msg = comment;
|
|
1922
|
+
if (atTokens) msg = comment + ' ' + atTokens;
|
|
1923
|
+
sendPasteToTerm(msg);
|
|
1924
|
+
} else if (atTokens) {
|
|
1925
|
+
// Stage at cursor — no Enter, so the user can type their question
|
|
1926
|
+
sendRawToTerm(atTokens + ' ');
|
|
1927
|
+
}
|
|
1928
|
+
showToast('Sent ' + files.length + ' file' + (files.length === 1 ? '' : 's') +
|
|
1929
|
+
(dirs.length ? ' and ' + dirs.length + ' folder' + (dirs.length === 1 ? '' : 's') : '') +
|
|
1930
|
+
' to chat');
|
|
1931
|
+
// Clear comment, clear index, refocus terminal
|
|
1932
|
+
var cmt = document.getElementById('idxComment'); if (cmt) cmt.value = '';
|
|
1933
|
+
clearIndex();
|
|
1934
|
+
try { term.focus(); } catch(e) {}
|
|
1935
|
+
};
|
|
1936
|
+
|
|
1751
1937
|
window.askAboutSelection = async function() {
|
|
1752
1938
|
if (!state.currentFile) return;
|
|
1753
1939
|
var sel = getCurrentSelection();
|
package/sapper.mjs
CHANGED
|
@@ -8634,31 +8634,31 @@ async function runSapper() {
|
|
|
8634
8634
|
spinner.start('Thinking...');
|
|
8635
8635
|
const aiStartTime = Date.now();
|
|
8636
8636
|
let response;
|
|
8637
|
-
|
|
8638
|
-
|
|
8639
|
-
|
|
8640
|
-
|
|
8641
|
-
|
|
8642
|
-
|
|
8643
|
-
|
|
8644
|
-
|
|
8645
|
-
if
|
|
8646
|
-
|
|
8647
|
-
|
|
8648
|
-
|
|
8649
|
-
|
|
8650
|
-
|
|
8651
|
-
|
|
8652
|
-
|
|
8653
|
-
|
|
8654
|
-
|
|
8655
|
-
|
|
8656
|
-
|
|
8657
|
-
|
|
8658
|
-
|
|
8659
|
-
chatOpts.tools = nativeToolDefs;
|
|
8660
|
-
}
|
|
8637
|
+
// Build chat options — pass native tools when supported
|
|
8638
|
+
const chatOpts = { model: selectedModel, messages, stream: true };
|
|
8639
|
+
if (effectiveContextLength()) {
|
|
8640
|
+
chatOpts.options = { num_ctx: effectiveContextLength() };
|
|
8641
|
+
}
|
|
8642
|
+
// Thinking can be forced on, forced off, or auto-disabled for simple prompts.
|
|
8643
|
+
if (turnThinkingEnabled) chatOpts.think = true;
|
|
8644
|
+
if (useNativeTools) {
|
|
8645
|
+
// Filter tool defs by agent restrictions if any
|
|
8646
|
+
if (currentAgentTools) {
|
|
8647
|
+
const toolNameMap = {
|
|
8648
|
+
list_directory: 'LIST', read_file: 'READ', search_files: 'SEARCH',
|
|
8649
|
+
write_file: 'WRITE', patch_file: 'PATCH', create_directory: 'MKDIR',
|
|
8650
|
+
ls: 'LS', cat: 'CAT', head: 'HEAD', tail: 'TAIL', grep: 'GREP', find: 'FIND',
|
|
8651
|
+
pwd: 'PWD', cd: 'CD', rmdir: 'RMDIR', changes: 'CHANGES',
|
|
8652
|
+
fetch_web: 'FETCH', recall_memory: 'MEMORY', open_url: 'OPEN', run_shell: 'SHELL'
|
|
8653
|
+
};
|
|
8654
|
+
chatOpts.tools = nativeToolDefs.filter(t =>
|
|
8655
|
+
isToolAllowedForAgent(currentAgentTools, toolNameMap[t.function.name])
|
|
8656
|
+
);
|
|
8657
|
+
} else {
|
|
8658
|
+
chatOpts.tools = nativeToolDefs;
|
|
8661
8659
|
}
|
|
8660
|
+
}
|
|
8661
|
+
try {
|
|
8662
8662
|
response = await ollama.chat(chatOpts);
|
|
8663
8663
|
} catch (ollamaError) {
|
|
8664
8664
|
const errMsg = ollamaError && ollamaError.message ? ollamaError.message : String(ollamaError);
|