sapper-iq 1.4.1 → 1.4.2
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 +63 -26
package/package.json
CHANGED
package/sapper-ui.mjs
CHANGED
|
@@ -628,6 +628,35 @@ function buildHTML() {
|
|
|
628
628
|
.tmsg.warn { border-color: rgba(210,153,34,.5); }
|
|
629
629
|
.tmsg.err { border-color: var(--red); }
|
|
630
630
|
@keyframes slideIn { from { transform: translateX(10px); opacity: 0; } to { transform: none; opacity: 1; } }
|
|
631
|
+
|
|
632
|
+
/* ─── Tooltip (! icon) ─── */
|
|
633
|
+
.tip {
|
|
634
|
+
display: inline-flex; align-items: center; justify-content: center;
|
|
635
|
+
width: 13px; height: 13px; border-radius: 50%;
|
|
636
|
+
border: 1px solid var(--border2); color: var(--dim);
|
|
637
|
+
font-size: 9px; font-weight: 700; cursor: help;
|
|
638
|
+
flex-shrink: 0; user-select: none; position: relative;
|
|
639
|
+
vertical-align: middle; margin-left: 4px; line-height: 1;
|
|
640
|
+
transition: color .12s, border-color .12s;
|
|
641
|
+
}
|
|
642
|
+
.tip:hover { color: var(--accent); border-color: var(--accent); }
|
|
643
|
+
.tip::after {
|
|
644
|
+
content: attr(data-tip);
|
|
645
|
+
position: absolute;
|
|
646
|
+
bottom: calc(100% + 7px);
|
|
647
|
+
left: 50%; transform: translateX(-50%);
|
|
648
|
+
background: var(--panel2); color: var(--fg);
|
|
649
|
+
border: 1px solid var(--border2);
|
|
650
|
+
border-radius: 6px; padding: 6px 10px;
|
|
651
|
+
font-size: 11px; font-weight: 400;
|
|
652
|
+
white-space: normal; width: 220px; line-height: 1.45;
|
|
653
|
+
pointer-events: none; opacity: 0;
|
|
654
|
+
z-index: 99999; box-shadow: 0 6px 20px rgba(0,0,0,.5);
|
|
655
|
+
transition: opacity .15s;
|
|
656
|
+
}
|
|
657
|
+
.tip:hover::after { opacity: 1; }
|
|
658
|
+
/* keep tip visible when near top of panel (flip below) */
|
|
659
|
+
.tip.below::after { bottom: auto; top: calc(100% + 7px); }
|
|
631
660
|
</style>
|
|
632
661
|
</head>
|
|
633
662
|
<body>
|
|
@@ -656,17 +685,17 @@ function buildHTML() {
|
|
|
656
685
|
<!-- Sidebar -->
|
|
657
686
|
<aside id="side">
|
|
658
687
|
<div class="tabs">
|
|
659
|
-
<button class="active" data-tab="files" onclick="switchTab('files')">Files</button>
|
|
660
|
-
<button data-tab="config" onclick="switchTab('config')">Config</button>
|
|
661
|
-
<button data-tab="agents" onclick="switchTab('agents')">Agents</button>
|
|
662
|
-
<button data-tab="skills" onclick="switchTab('skills')">Skills</button>
|
|
688
|
+
<button class="active" data-tab="files" onclick="switchTab('files')" title="Browse and manage workspace files">Files</button>
|
|
689
|
+
<button data-tab="config" onclick="switchTab('config')" title="Edit Sapper config.json settings">Config</button>
|
|
690
|
+
<button data-tab="agents" onclick="switchTab('agents')" title="Manage AI agent roles (.sapper/agents/)">Agents</button>
|
|
691
|
+
<button data-tab="skills" onclick="switchTab('skills')" title="Manage skills loaded into the AI (.sapper/skills/)">Skills</button>
|
|
663
692
|
</div>
|
|
664
693
|
<div class="pane active" id="pane-files">
|
|
665
694
|
<div class="files-toolbar">
|
|
666
|
-
<button class="ftb" title="
|
|
667
|
-
<button class="ftb" title="
|
|
668
|
-
<button class="ftb" id="ftbAct" title="
|
|
669
|
-
<button class="ftb" id="ftbIdx" title="Index files/folders into chat
|
|
695
|
+
<button class="ftb" title="Create a new file in the current directory" onclick="newItemPrompt('file','')">🗎<sup>+</sup></button>
|
|
696
|
+
<button class="ftb" title="Create a new folder in the current directory" onclick="newItemPrompt('folder','')">📁<sup>+</sup></button>
|
|
697
|
+
<button class="ftb" id="ftbAct" title="Toggle activity feed — shows files recently created, modified, or deleted by Sapper" onclick="toggleActivity()">☉</button>
|
|
698
|
+
<button class="ftb" id="ftbIdx" title="Index mode — check files/folders to bundle them into a single chat message" onclick="toggleIndexMode()">📚</button>
|
|
670
699
|
<span class="ftb-spacer"></span>
|
|
671
700
|
<button class="ftb" title="Clear change marks" onclick="clearAllMarks()">✕</button>
|
|
672
701
|
<button class="ftb" title="Refresh tree" onclick="loadTree()">↺</button>
|
|
@@ -725,18 +754,18 @@ function buildHTML() {
|
|
|
725
754
|
<!-- Center: terminal -->
|
|
726
755
|
<main id="center">
|
|
727
756
|
<div id="qa">
|
|
728
|
-
<button class="qabtn" title="
|
|
757
|
+
<button class="qabtn" title="Pick local files to attach — sends @filepath reference(s) into the terminal" onclick="pickAndUpload()">
|
|
729
758
|
<span class="qaico">📎</span><span class="qalbl">Attach</span>
|
|
730
759
|
</button>
|
|
731
|
-
<button class="qabtn rec" title="Record voice
|
|
760
|
+
<button class="qabtn rec" title="Record a voice message — Sapper transcribes it with Whisper and sends it as a prompt" onclick="toggleRecord()" id="qaRec">
|
|
732
761
|
<span class="qaico">🎤</span><span class="qalbl">Record</span>
|
|
733
762
|
</button>
|
|
734
763
|
<span id="recDot" class="rec-dot"></span>
|
|
735
764
|
<span id="recTime" class="rec-time"></span>
|
|
736
765
|
<span class="qa-sp"></span>
|
|
737
|
-
<button class="qabtn" title="
|
|
738
|
-
<button class="qabtn" title="Open file by path" onclick="sendOpenPrompt()">/open</button>
|
|
739
|
-
<button class="qabtn" title="
|
|
766
|
+
<button class="qabtn" title="Run /attach inside the terminal — lets Sapper interactively pick files with fuzzy search" onclick="sendCmd('/attach')">/attach</button>
|
|
767
|
+
<button class="qabtn" title="Open a file by path in the preview panel" onclick="sendOpenPrompt()">/open</button>
|
|
768
|
+
<button class="qabtn" title="Summarize and compress the conversation context to free up token space" onclick="sendCmd('/summary')">/summary</button>
|
|
740
769
|
<input type="file" id="qaFile" multiple style="display:none">
|
|
741
770
|
</div>
|
|
742
771
|
<div id="term-wrap"></div>
|
|
@@ -2478,14 +2507,15 @@ function renderQuickConfig(cfg) {
|
|
|
2478
2507
|
var host = document.getElementById('cfgQuickBody');
|
|
2479
2508
|
host.innerHTML = '';
|
|
2480
2509
|
function add(html) { host.insertAdjacentHTML('beforeend', html); }
|
|
2481
|
-
|
|
2482
|
-
add('<label>Default
|
|
2483
|
-
add('<label>
|
|
2484
|
-
add('<label>
|
|
2485
|
-
add('<
|
|
2486
|
-
add('<
|
|
2487
|
-
add('<
|
|
2488
|
-
add('<div class="toggle-row"><span>
|
|
2510
|
+
var T = function(tip, below) { return '<span class="tip' + (below ? ' below' : '') + '" data-tip="' + tip.replace(/"/g, '"') + '">!</span>'; };
|
|
2511
|
+
add('<label>Default model ' + T('The AI model used when no model is specified at startup. E.g. gpt-4o, claude-3-5-sonnet, qwen3.5. Leave blank to be prompted on launch.') + '</label><input type="text" id="qDefMod" placeholder="auto" value="' + esc(cfg.defaultModel || '') + '">');
|
|
2512
|
+
add('<label>Default agent ' + T('Agent role (.sapper/agents/*.md) to activate automatically when Sapper starts. Leave blank for the default general-purpose assistant.') + '</label><input type="text" id="qDefAgent" placeholder="(none)" value="' + esc(cfg.defaultAgent || '') + '">');
|
|
2513
|
+
add('<label>Context limit ' + T('Hard cap on tokens sent to the model per request. Leave blank to use the model\'s full context window. Useful to reduce cost or avoid slow responses.') + '</label><input type="number" id="qCtxLim" value="' + esc(cfg.contextLimit == null ? '' : cfg.contextLimit) + '">');
|
|
2514
|
+
add('<label>Tool round limit ' + T('Maximum number of tool calls (file reads, shell commands, patches…) Sapper may make in a single response turn. Default is 40. Lower to prevent runaway loops.') + '</label><input type="number" id="qToolRnd" value="' + esc(cfg.toolRoundLimit != null ? cfg.toolRoundLimit : 40) + '">');
|
|
2515
|
+
add('<div class="toggle-row"><span>Summary phases ' + T('When ON, Sapper displays a step-by-step progress bar while it compresses long conversations. Turn OFF for a quieter experience.') + '</span><div class="switch ' + (cfg.summaryPhases ? 'on' : '') + '" id="qSumPh"></div></div>');
|
|
2516
|
+
add('<label>Summary trigger % ' + T('When the conversation reaches this percentage of the context window, Sapper automatically summarizes older messages to keep the window from overflowing. Default 65%.') + '</label><input type="number" id="qSumTr" value="' + esc(cfg.summarizeTriggerPercent != null ? cfg.summarizeTriggerPercent : 65) + '">');
|
|
2517
|
+
add('<div class="toggle-row"><span>Debug mode ' + T('Enables verbose output — shows raw tool call details, API request sizes, and internal errors. Useful for troubleshooting but noisy during normal use.') + '</span><div class="switch ' + (cfg.debug ? 'on' : '') + '" id="qDebug"></div></div>');
|
|
2518
|
+
add('<div class="toggle-row"><span>Auto-attach files ' + T('When ON, files you open in the sidebar are automatically referenced in the AI context so Sapper knows what you are looking at without you typing @filename.') + '</span><div class="switch ' + (cfg.autoAttach !== false ? 'on' : '') + '" id="qAutoAtt"></div></div>');
|
|
2489
2519
|
add('<div class="row-btns"><button class="primary" onclick="saveQuickConfig()">Apply quick changes</button></div>');
|
|
2490
2520
|
|
|
2491
2521
|
function bindSwitch(id) {
|
|
@@ -2837,13 +2867,20 @@ function listEntries(dirPath) {
|
|
|
2837
2867
|
|
|
2838
2868
|
function looksBinary(buf) {
|
|
2839
2869
|
const len = Math.min(buf.length, 4096);
|
|
2840
|
-
|
|
2870
|
+
// Null byte is a definitive binary indicator
|
|
2841
2871
|
for (let i = 0; i < len; i++) {
|
|
2842
|
-
|
|
2843
|
-
|
|
2844
|
-
|
|
2872
|
+
if (buf[i] === 0) return true;
|
|
2873
|
+
}
|
|
2874
|
+
// Try decoding as UTF-8; replacement char U+FFFD signals invalid sequences (binary)
|
|
2875
|
+
const sample = buf.slice(0, len).toString('utf8');
|
|
2876
|
+
if (sample.includes('\uFFFD')) return true;
|
|
2877
|
+
// Count true non-printable control chars (bytes < 32 excluding tab/LF/CR)
|
|
2878
|
+
let nonText = 0;
|
|
2879
|
+
for (let i = 0; i < sample.length; i++) {
|
|
2880
|
+
const c = sample.charCodeAt(i);
|
|
2881
|
+
if (c < 32 && c !== 9 && c !== 10 && c !== 13) nonText++;
|
|
2845
2882
|
}
|
|
2846
|
-
return nonText / Math.max(
|
|
2883
|
+
return nonText / Math.max(sample.length, 1) > 0.1;
|
|
2847
2884
|
}
|
|
2848
2885
|
|
|
2849
2886
|
const server = http.createServer(async (req, res) => {
|