agentgui 1.0.252 → 1.0.254
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/server.js +57 -17
- package/static/app.js +2 -2
- package/static/index.html +58 -7
- package/static/js/client.js +21 -39
- package/static/js/conversations.js +6 -4
- package/static/js/dialogs.js +267 -0
- package/static/js/features.js +42 -6
- package/static/js/streaming-renderer.js +26 -14
package/package.json
CHANGED
package/server.js
CHANGED
|
@@ -306,6 +306,7 @@ const discoveredAgents = discoverAgents();
|
|
|
306
306
|
const modelCache = new Map();
|
|
307
307
|
|
|
308
308
|
const AGENT_MODEL_COMMANDS = {
|
|
309
|
+
'claude-code': 'claude models',
|
|
309
310
|
'opencode': 'opencode models',
|
|
310
311
|
'kilo': 'kilo models',
|
|
311
312
|
};
|
|
@@ -317,6 +318,7 @@ const AGENT_DEFAULT_MODELS = {
|
|
|
317
318
|
{ id: 'opus', label: 'Opus' },
|
|
318
319
|
{ id: 'haiku', label: 'Haiku' },
|
|
319
320
|
{ id: 'claude-sonnet-4-5-20250929', label: 'Sonnet 4.5' },
|
|
321
|
+
{ id: 'claude-sonnet-4-6-20260219', label: 'Sonnet 4.6' },
|
|
320
322
|
{ id: 'claude-opus-4-6', label: 'Opus 4.6' },
|
|
321
323
|
{ id: 'claude-haiku-4-5-20251001', label: 'Haiku 4.5' }
|
|
322
324
|
],
|
|
@@ -390,25 +392,25 @@ async function getModelsForAgent(agentId) {
|
|
|
390
392
|
return models;
|
|
391
393
|
}
|
|
392
394
|
|
|
393
|
-
if (AGENT_DEFAULT_MODELS[agentId]) {
|
|
394
|
-
const models = AGENT_DEFAULT_MODELS[agentId];
|
|
395
|
-
modelCache.set(agentId, { models, timestamp: Date.now() });
|
|
396
|
-
return models;
|
|
397
|
-
}
|
|
398
|
-
|
|
399
395
|
if (AGENT_MODEL_COMMANDS[agentId]) {
|
|
400
396
|
try {
|
|
401
397
|
const result = execSync(AGENT_MODEL_COMMANDS[agentId], { encoding: 'utf-8', timeout: 15000 });
|
|
402
398
|
const lines = result.split('\n').map(l => l.trim()).filter(Boolean);
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
399
|
+
if (lines.length > 0) {
|
|
400
|
+
const models = [{ id: '', label: 'Default' }];
|
|
401
|
+
for (const line of lines) {
|
|
402
|
+
models.push({ id: line, label: line });
|
|
403
|
+
}
|
|
404
|
+
modelCache.set(agentId, { models, timestamp: Date.now() });
|
|
405
|
+
return models;
|
|
406
406
|
}
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
407
|
+
} catch (_) {}
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
if (AGENT_DEFAULT_MODELS[agentId]) {
|
|
411
|
+
const models = AGENT_DEFAULT_MODELS[agentId];
|
|
412
|
+
modelCache.set(agentId, { models, timestamp: Date.now() });
|
|
413
|
+
return models;
|
|
412
414
|
}
|
|
413
415
|
|
|
414
416
|
const { getRegisteredAgents } = await import('./lib/claude-runner.js');
|
|
@@ -1970,6 +1972,41 @@ const server = http.createServer(async (req, res) => {
|
|
|
1970
1972
|
return;
|
|
1971
1973
|
}
|
|
1972
1974
|
|
|
1975
|
+
const agentUpdateMatch = pathOnly.match(/^\/api\/agents\/([^/]+)\/update$/);
|
|
1976
|
+
if (agentUpdateMatch && req.method === 'POST') {
|
|
1977
|
+
const agentId = agentUpdateMatch[1];
|
|
1978
|
+
const updateCommands = {
|
|
1979
|
+
'claude-code': { cmd: 'claude', args: ['update', '--yes'] },
|
|
1980
|
+
};
|
|
1981
|
+
const updateCmd = updateCommands[agentId];
|
|
1982
|
+
if (!updateCmd) { sendJSON(req, res, 400, { error: 'No update command for this agent' }); return; }
|
|
1983
|
+
const conversationId = '__agent_update__';
|
|
1984
|
+
if (activeScripts.has(conversationId)) { sendJSON(req, res, 409, { error: 'Update already running' }); return; }
|
|
1985
|
+
const child = spawn(updateCmd.cmd, updateCmd.args, {
|
|
1986
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
1987
|
+
env: { ...process.env, FORCE_COLOR: '1' },
|
|
1988
|
+
shell: os.platform() === 'win32'
|
|
1989
|
+
});
|
|
1990
|
+
activeScripts.set(conversationId, { process: child, script: 'update-' + agentId, startTime: Date.now() });
|
|
1991
|
+
broadcastSync({ type: 'script_started', conversationId, script: 'update-' + agentId, agentId, timestamp: Date.now() });
|
|
1992
|
+
const onData = (stream) => (chunk) => {
|
|
1993
|
+
broadcastSync({ type: 'script_output', conversationId, data: chunk.toString(), stream, timestamp: Date.now() });
|
|
1994
|
+
};
|
|
1995
|
+
child.stdout.on('data', onData('stdout'));
|
|
1996
|
+
child.stderr.on('data', onData('stderr'));
|
|
1997
|
+
child.on('error', (err) => {
|
|
1998
|
+
activeScripts.delete(conversationId);
|
|
1999
|
+
broadcastSync({ type: 'script_stopped', conversationId, code: 1, error: err.message, timestamp: Date.now() });
|
|
2000
|
+
});
|
|
2001
|
+
child.on('close', (code) => {
|
|
2002
|
+
activeScripts.delete(conversationId);
|
|
2003
|
+
modelCache.delete(agentId);
|
|
2004
|
+
broadcastSync({ type: 'script_stopped', conversationId, code: code || 0, timestamp: Date.now() });
|
|
2005
|
+
});
|
|
2006
|
+
sendJSON(req, res, 200, { ok: true, agentId, pid: child.pid });
|
|
2007
|
+
return;
|
|
2008
|
+
}
|
|
2009
|
+
|
|
1973
2010
|
if (pathOnly === '/api/auth/configs' && req.method === 'GET') {
|
|
1974
2011
|
const configs = getProviderConfigs();
|
|
1975
2012
|
sendJSON(req, res, 200, configs);
|
|
@@ -2555,9 +2592,9 @@ const server = http.createServer(async (req, res) => {
|
|
|
2555
2592
|
const isWindows = os.platform() === 'win32';
|
|
2556
2593
|
const result = execSync('git remote get-url origin' + (isWindows ? '' : ' 2>/dev/null'), { encoding: 'utf-8', cwd: STARTUP_CWD, shell: isWindows });
|
|
2557
2594
|
const remoteUrl = result.trim();
|
|
2558
|
-
const statusResult = execSync('git status --porcelain', { encoding: 'utf-8', cwd: STARTUP_CWD });
|
|
2595
|
+
const statusResult = execSync('git status --porcelain' + (isWindows ? '' : ' 2>/dev/null'), { encoding: 'utf-8', cwd: STARTUP_CWD, shell: isWindows });
|
|
2559
2596
|
const hasChanges = statusResult.trim().length > 0;
|
|
2560
|
-
const unpushedResult = execSync('git rev-list --count --not --remotes 2>/dev/null', { encoding: 'utf-8', cwd: STARTUP_CWD });
|
|
2597
|
+
const unpushedResult = execSync('git rev-list --count --not --remotes' + (isWindows ? '' : ' 2>/dev/null'), { encoding: 'utf-8', cwd: STARTUP_CWD, shell: isWindows });
|
|
2561
2598
|
const hasUnpushed = parseInt(unpushedResult.trim() || '0', 10) > 0;
|
|
2562
2599
|
const ownsRemote = !remoteUrl.includes('github.com/') || remoteUrl.includes(process.env.GITHUB_USER || '');
|
|
2563
2600
|
sendJSON(req, res, 200, { ownsRemote, hasChanges, hasUnpushed, remoteUrl });
|
|
@@ -2570,7 +2607,10 @@ const server = http.createServer(async (req, res) => {
|
|
|
2570
2607
|
if (pathOnly === '/api/git/push' && req.method === 'POST') {
|
|
2571
2608
|
try {
|
|
2572
2609
|
const isWindows = os.platform() === 'win32';
|
|
2573
|
-
|
|
2610
|
+
const gitCommand = isWindows
|
|
2611
|
+
? 'git add -A & git commit -m "Auto-commit" & git push'
|
|
2612
|
+
: 'git add -A && git commit -m "Auto-commit" && git push';
|
|
2613
|
+
execSync(gitCommand, { encoding: 'utf-8', cwd: STARTUP_CWD, shell: isWindows });
|
|
2574
2614
|
sendJSON(req, res, 200, { success: true });
|
|
2575
2615
|
} catch (err) {
|
|
2576
2616
|
sendJSON(req, res, 500, { error: err.message });
|
package/static/app.js
CHANGED
|
@@ -678,8 +678,8 @@ function closeNewChatModal() {
|
|
|
678
678
|
}
|
|
679
679
|
}
|
|
680
680
|
|
|
681
|
-
function createChatInWorkspace() {
|
|
682
|
-
const title = prompt('Enter a title for the conversation:', 'New Conversation');
|
|
681
|
+
async function createChatInWorkspace() {
|
|
682
|
+
const title = await window.UIDialog.prompt('Enter a title for the conversation:', 'New Conversation', 'New Chat');
|
|
683
683
|
if (title) {
|
|
684
684
|
app.createConversation(title);
|
|
685
685
|
closeNewChatModal();
|
package/static/index.html
CHANGED
|
@@ -1753,11 +1753,11 @@
|
|
|
1753
1753
|
display: flex;
|
|
1754
1754
|
align-items: center;
|
|
1755
1755
|
gap: 0.375rem;
|
|
1756
|
-
padding: 0.
|
|
1756
|
+
padding: 0.4rem 0.75rem;
|
|
1757
1757
|
cursor: pointer;
|
|
1758
1758
|
user-select: none;
|
|
1759
1759
|
list-style: none;
|
|
1760
|
-
font-size: 0.
|
|
1760
|
+
font-size: 0.85rem;
|
|
1761
1761
|
line-height: 1.3;
|
|
1762
1762
|
background: #dcfce7;
|
|
1763
1763
|
transition: background 0.15s;
|
|
@@ -1784,24 +1784,24 @@
|
|
|
1784
1784
|
display: flex;
|
|
1785
1785
|
align-items: center;
|
|
1786
1786
|
color: #16a34a;
|
|
1787
|
-
width:
|
|
1788
|
-
height:
|
|
1787
|
+
width: 1rem;
|
|
1788
|
+
height: 1rem;
|
|
1789
1789
|
flex-shrink: 0;
|
|
1790
1790
|
}
|
|
1791
1791
|
html.dark .folded-tool-icon { color: #4ade80; }
|
|
1792
|
-
.folded-tool-icon svg { width:
|
|
1792
|
+
.folded-tool-icon svg { width: 1rem; height: 1rem; }
|
|
1793
1793
|
.folded-tool-name {
|
|
1794
1794
|
font-weight: 600;
|
|
1795
1795
|
color: #166534;
|
|
1796
1796
|
font-family: 'Monaco','Menlo','Ubuntu Mono', monospace;
|
|
1797
|
-
font-size: 0.
|
|
1797
|
+
font-size: 0.8rem;
|
|
1798
1798
|
flex-shrink: 0;
|
|
1799
1799
|
}
|
|
1800
1800
|
html.dark .folded-tool-name { color: #86efac; }
|
|
1801
1801
|
.folded-tool-desc {
|
|
1802
1802
|
color: #15803d;
|
|
1803
1803
|
font-family: 'Monaco','Menlo','Ubuntu Mono', monospace;
|
|
1804
|
-
font-size: 0.
|
|
1804
|
+
font-size: 0.8rem;
|
|
1805
1805
|
overflow: hidden;
|
|
1806
1806
|
text-overflow: ellipsis;
|
|
1807
1807
|
white-space: nowrap;
|
|
@@ -1874,6 +1874,33 @@
|
|
|
1874
1874
|
.folded-tool-info > .folded-tool-body { border-top-color: #c7d2fe; }
|
|
1875
1875
|
html.dark .folded-tool-info > .folded-tool-body { border-top-color: #3730a3; }
|
|
1876
1876
|
|
|
1877
|
+
/* --- Consecutive block joining --- */
|
|
1878
|
+
.folded-tool + .folded-tool,
|
|
1879
|
+
.block-tool-use + .block-tool-use {
|
|
1880
|
+
margin-top: -1px;
|
|
1881
|
+
border-radius: 0;
|
|
1882
|
+
}
|
|
1883
|
+
.folded-tool + .folded-tool > .folded-tool-bar,
|
|
1884
|
+
.block-tool-use + .block-tool-use > .folded-tool-bar {
|
|
1885
|
+
border-top: 1px solid rgba(0,0,0,0.06);
|
|
1886
|
+
}
|
|
1887
|
+
html.dark .folded-tool + .folded-tool > .folded-tool-bar,
|
|
1888
|
+
html.dark .block-tool-use + .block-tool-use > .folded-tool-bar {
|
|
1889
|
+
border-top: 1px solid rgba(255,255,255,0.06);
|
|
1890
|
+
}
|
|
1891
|
+
.folded-tool:first-child,
|
|
1892
|
+
.block-tool-use:first-child {
|
|
1893
|
+
border-radius: 0.375rem 0.375rem 0 0;
|
|
1894
|
+
}
|
|
1895
|
+
.folded-tool:last-child,
|
|
1896
|
+
.block-tool-use:last-child {
|
|
1897
|
+
border-radius: 0 0 0.375rem 0.375rem;
|
|
1898
|
+
}
|
|
1899
|
+
.folded-tool:only-child,
|
|
1900
|
+
.block-tool-use:only-child {
|
|
1901
|
+
border-radius: 0.375rem;
|
|
1902
|
+
}
|
|
1903
|
+
|
|
1877
1904
|
/* --- Inline Tool Result (nested inside tool_use) --- */
|
|
1878
1905
|
.tool-result-inline {
|
|
1879
1906
|
border-top: 1px solid #bbf7d0;
|
|
@@ -1905,6 +1932,30 @@
|
|
|
1905
1932
|
.tool-result-error > .folded-tool-body { border-top-color: #fecaca; }
|
|
1906
1933
|
html.dark .tool-result-error > .folded-tool-body { border-top-color: #991b1b; }
|
|
1907
1934
|
|
|
1935
|
+
/* --- Consecutive Block Joining --- */
|
|
1936
|
+
.streaming-blocks > * + *,
|
|
1937
|
+
.message-blocks > * + * {
|
|
1938
|
+
margin-top: 0;
|
|
1939
|
+
}
|
|
1940
|
+
.streaming-blocks > *,
|
|
1941
|
+
.message-blocks > * {
|
|
1942
|
+
border-radius: 0;
|
|
1943
|
+
}
|
|
1944
|
+
.streaming-blocks > *:first-child,
|
|
1945
|
+
.message-blocks > *:first-child {
|
|
1946
|
+
border-top-left-radius: 0.375rem;
|
|
1947
|
+
border-top-right-radius: 0.375rem;
|
|
1948
|
+
}
|
|
1949
|
+
.streaming-blocks > *:last-child,
|
|
1950
|
+
.message-blocks > *:last-child {
|
|
1951
|
+
border-bottom-left-radius: 0.375rem;
|
|
1952
|
+
border-bottom-right-radius: 0.375rem;
|
|
1953
|
+
}
|
|
1954
|
+
.streaming-blocks > *:only-child,
|
|
1955
|
+
.message-blocks > *:only-child {
|
|
1956
|
+
border-radius: 0.375rem;
|
|
1957
|
+
}
|
|
1958
|
+
|
|
1908
1959
|
/* --- Collapsible Code Summary --- */
|
|
1909
1960
|
.collapsible-code {
|
|
1910
1961
|
margin: 0;
|
package/static/js/client.js
CHANGED
|
@@ -315,14 +315,24 @@ class AgentGUIClient {
|
|
|
315
315
|
const scrollContainer = document.getElementById(this.config.scrollContainerId);
|
|
316
316
|
if (!scrollContainer) return;
|
|
317
317
|
|
|
318
|
+
this._userScrolledUp = false;
|
|
318
319
|
let scrollTimer = null;
|
|
320
|
+
let lastScrollTop = scrollContainer.scrollTop;
|
|
319
321
|
scrollContainer.addEventListener('scroll', () => {
|
|
322
|
+
const distFromBottom = scrollContainer.scrollHeight - scrollContainer.scrollTop - scrollContainer.clientHeight;
|
|
323
|
+
if (scrollContainer.scrollTop < lastScrollTop && distFromBottom > 200) {
|
|
324
|
+
this._userScrolledUp = true;
|
|
325
|
+
} else if (distFromBottom < 50) {
|
|
326
|
+
this._userScrolledUp = false;
|
|
327
|
+
this._removeNewContentPill();
|
|
328
|
+
}
|
|
329
|
+
lastScrollTop = scrollContainer.scrollTop;
|
|
320
330
|
if (scrollTimer) clearTimeout(scrollTimer);
|
|
321
331
|
scrollTimer = setTimeout(() => {
|
|
322
332
|
if (this.state.currentConversation?.id) {
|
|
323
333
|
this.saveScrollPosition(this.state.currentConversation.id);
|
|
324
334
|
}
|
|
325
|
-
}, 500);
|
|
335
|
+
}, 500);
|
|
326
336
|
});
|
|
327
337
|
}
|
|
328
338
|
|
|
@@ -706,50 +716,21 @@ class AgentGUIClient {
|
|
|
706
716
|
if (!scrollContainer) return;
|
|
707
717
|
const distFromBottom = scrollContainer.scrollHeight - scrollContainer.scrollTop - scrollContainer.clientHeight;
|
|
708
718
|
|
|
709
|
-
if (
|
|
719
|
+
if (this._userScrolledUp && !force) {
|
|
710
720
|
this._unseenCount = (this._unseenCount || 0) + 1;
|
|
711
721
|
this._showNewContentPill();
|
|
712
722
|
return;
|
|
713
723
|
}
|
|
714
724
|
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
if (!isStreaming || !this._scrollKalman || Math.abs(maxScroll - scrollContainer.scrollTop) > 2000 || force) {
|
|
719
|
-
scrollContainer.scrollTop = scrollContainer.scrollHeight;
|
|
720
|
-
this._removeNewContentPill();
|
|
721
|
-
this._scrollAnimating = false;
|
|
725
|
+
if (!force && distFromBottom > 150) {
|
|
726
|
+
this._unseenCount = (this._unseenCount || 0) + 1;
|
|
727
|
+
this._showNewContentPill();
|
|
722
728
|
return;
|
|
723
729
|
}
|
|
724
730
|
|
|
725
|
-
|
|
726
|
-
this.
|
|
727
|
-
|
|
728
|
-
const conf = this._chunkArrivalConfidence();
|
|
729
|
-
if (conf > 0.5) {
|
|
730
|
-
const estHeight = this._estimatedBlockHeight('text') * 0.5 * conf;
|
|
731
|
-
this._scrollTarget += estHeight;
|
|
732
|
-
const trueMax = scrollContainer.scrollHeight - scrollContainer.clientHeight;
|
|
733
|
-
if (this._scrollTarget > trueMax + 100) this._scrollTarget = trueMax + 100;
|
|
734
|
-
}
|
|
735
|
-
|
|
736
|
-
if (!this._scrollAnimating) {
|
|
737
|
-
this._scrollAnimating = true;
|
|
738
|
-
const animate = () => {
|
|
739
|
-
if (!this._scrollAnimating) return;
|
|
740
|
-
const sc = document.getElementById('output-scroll');
|
|
741
|
-
if (!sc) { this._scrollAnimating = false; return; }
|
|
742
|
-
const diff = this._scrollTarget - sc.scrollTop;
|
|
743
|
-
if (Math.abs(diff) < 1) {
|
|
744
|
-
sc.scrollTop = this._scrollTarget;
|
|
745
|
-
if (this.state.streamingConversations.size === 0) { this._scrollAnimating = false; return; }
|
|
746
|
-
}
|
|
747
|
-
sc.scrollTop += diff * this._scrollLerpFactor;
|
|
748
|
-
this._removeNewContentPill();
|
|
749
|
-
requestAnimationFrame(animate);
|
|
750
|
-
};
|
|
751
|
-
requestAnimationFrame(animate);
|
|
752
|
-
}
|
|
731
|
+
scrollContainer.scrollTop = scrollContainer.scrollHeight;
|
|
732
|
+
this._removeNewContentPill();
|
|
733
|
+
this._scrollAnimating = false;
|
|
753
734
|
}
|
|
754
735
|
|
|
755
736
|
_showNewContentPill() {
|
|
@@ -1205,11 +1186,11 @@ class AgentGUIClient {
|
|
|
1205
1186
|
const tTitle = hasRenderer && block.input ? StreamingRenderer.getToolTitle(tn, block.input) : '';
|
|
1206
1187
|
const iconHtml = hasRenderer && this.renderer ? `<span class="folded-tool-icon">${this.renderer.getToolIcon(tn)}</span>` : '';
|
|
1207
1188
|
const colorIdx = hasRenderer && this.renderer ? this.renderer._getBlockColorIndex('tool_use') : 1;
|
|
1208
|
-
html += `<details class="block-tool-use folded-tool"
|
|
1189
|
+
html += `<details class="block-tool-use folded-tool" style="border-left:3px solid var(--block-color-${colorIdx})"><summary class="folded-tool-bar">${iconHtml}<span class="folded-tool-name">${this.escapeHtml(dName)}</span>${tTitle ? `<span class="folded-tool-desc">${this.escapeHtml(tTitle)}</span>` : ''}</summary>${inputHtml}`;
|
|
1209
1190
|
pendingToolUseClose = true;
|
|
1210
1191
|
} else if (block.type === 'tool_result') {
|
|
1211
1192
|
const content = typeof block.content === 'string' ? block.content : JSON.stringify(block.content);
|
|
1212
|
-
const smartHtml = typeof StreamingRenderer !== 'undefined' ? StreamingRenderer.renderSmartContentHTML(content, this.escapeHtml.bind(this)) : `<pre class="tool-result-pre">${this.escapeHtml(content.length > 2000 ? content.substring(0, 2000) + '\n... (truncated)' : content)}</pre>`;
|
|
1193
|
+
const smartHtml = typeof StreamingRenderer !== 'undefined' ? StreamingRenderer.renderSmartContentHTML(content, this.escapeHtml.bind(this), true) : `<pre class="tool-result-pre">${this.escapeHtml(content.length > 2000 ? content.substring(0, 2000) + '\n... (truncated)' : content)}</pre>`;
|
|
1213
1194
|
const resultPreview = content.length > 80 ? content.substring(0, 77).replace(/\n/g, ' ') + '...' : content.replace(/\n/g, ' ');
|
|
1214
1195
|
const resultIcon = block.is_error
|
|
1215
1196
|
? '<svg viewBox="0 0 20 20" fill="currentColor"><path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z" clip-rule="evenodd"/></svg>'
|
|
@@ -2235,6 +2216,7 @@ class AgentGUIClient {
|
|
|
2235
2216
|
|
|
2236
2217
|
this.cacheCurrentConversation();
|
|
2237
2218
|
this.stopChunkPolling();
|
|
2219
|
+
if (this.renderer.resetScrollState) this.renderer.resetScrollState();
|
|
2238
2220
|
var prevId = this.state.currentConversation?.id;
|
|
2239
2221
|
if (prevId && prevId !== conversationId) {
|
|
2240
2222
|
if (this.wsManager.isConnected && !this.state.streamingConversations.has(prevId)) {
|
|
@@ -475,7 +475,10 @@ class ConversationManager {
|
|
|
475
475
|
}
|
|
476
476
|
|
|
477
477
|
async confirmDelete(convId, title) {
|
|
478
|
-
const confirmed = confirm(
|
|
478
|
+
const confirmed = await window.UIDialog.confirm(
|
|
479
|
+
`Delete conversation "${title || 'Untitled'}"?\n\nThis will also delete any associated Claude Code session data. This action cannot be undone.`,
|
|
480
|
+
'Delete Conversation'
|
|
481
|
+
);
|
|
479
482
|
if (!confirmed) return;
|
|
480
483
|
|
|
481
484
|
try {
|
|
@@ -486,15 +489,14 @@ class ConversationManager {
|
|
|
486
489
|
|
|
487
490
|
if (res.ok) {
|
|
488
491
|
console.log(`[ConversationManager] Deleted conversation ${convId}`);
|
|
489
|
-
// Remove from local list immediately for responsive UI
|
|
490
492
|
this.deleteConversation(convId);
|
|
491
493
|
} else {
|
|
492
494
|
const error = await res.json().catch(() => ({ error: 'Failed to delete' }));
|
|
493
|
-
alert('Failed to delete conversation: ' + (error.error || 'Unknown error'));
|
|
495
|
+
window.UIDialog.alert('Failed to delete conversation: ' + (error.error || 'Unknown error'), 'Error');
|
|
494
496
|
}
|
|
495
497
|
} catch (err) {
|
|
496
498
|
console.error('[ConversationManager] Delete error:', err);
|
|
497
|
-
alert('Failed to delete conversation: ' + err.message);
|
|
499
|
+
window.UIDialog.alert('Failed to delete conversation: ' + err.message, 'Error');
|
|
498
500
|
}
|
|
499
501
|
}
|
|
500
502
|
|
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
(function() {
|
|
2
|
+
var activeDialogs = [];
|
|
3
|
+
var dialogZIndex = 10000;
|
|
4
|
+
|
|
5
|
+
function escapeHtml(text) {
|
|
6
|
+
var map = { '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' };
|
|
7
|
+
return String(text).replace(/[&<>"']/g, function(c) { return map[c]; });
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function createOverlay() {
|
|
11
|
+
var overlay = document.createElement('div');
|
|
12
|
+
overlay.className = 'dialog-overlay';
|
|
13
|
+
overlay.innerHTML = '<div class="dialog-backdrop"></div>';
|
|
14
|
+
return overlay;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function showDialog(dialog, overlay) {
|
|
18
|
+
dialogZIndex++;
|
|
19
|
+
if (overlay) {
|
|
20
|
+
overlay.style.zIndex = dialogZIndex;
|
|
21
|
+
document.body.appendChild(overlay);
|
|
22
|
+
}
|
|
23
|
+
dialog.style.zIndex = dialogZIndex + 1;
|
|
24
|
+
document.body.appendChild(dialog);
|
|
25
|
+
activeDialogs.push({ dialog: dialog, overlay: overlay });
|
|
26
|
+
|
|
27
|
+
requestAnimationFrame(function() {
|
|
28
|
+
dialog.classList.add('visible');
|
|
29
|
+
if (overlay) overlay.classList.add('visible');
|
|
30
|
+
var input = dialog.querySelector('input, textarea');
|
|
31
|
+
if (input) input.focus();
|
|
32
|
+
else {
|
|
33
|
+
var btn = dialog.querySelector('.dialog-btn-primary');
|
|
34
|
+
if (btn) btn.focus();
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function closeDialog(dialog, overlay) {
|
|
40
|
+
dialog.classList.remove('visible');
|
|
41
|
+
if (overlay) overlay.classList.remove('visible');
|
|
42
|
+
setTimeout(function() {
|
|
43
|
+
if (dialog.parentNode) dialog.remove();
|
|
44
|
+
if (overlay && overlay.parentNode) overlay.remove();
|
|
45
|
+
}, 200);
|
|
46
|
+
activeDialogs = activeDialogs.filter(function(d) {
|
|
47
|
+
return d.dialog !== dialog;
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function closeAllDialogs() {
|
|
52
|
+
activeDialogs.forEach(function(d) {
|
|
53
|
+
closeDialog(d.dialog, d.overlay);
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
window.UIDialog = {
|
|
58
|
+
alert: function(message, title) {
|
|
59
|
+
return new Promise(function(resolve) {
|
|
60
|
+
var overlay = createOverlay();
|
|
61
|
+
var dialog = document.createElement('div');
|
|
62
|
+
dialog.className = 'dialog-container';
|
|
63
|
+
dialog.innerHTML =
|
|
64
|
+
'<div class="dialog-box">' +
|
|
65
|
+
'<div class="dialog-header">' +
|
|
66
|
+
'<h3 class="dialog-title">' + escapeHtml(title || 'Alert') + '</h3>' +
|
|
67
|
+
'</div>' +
|
|
68
|
+
'<div class="dialog-body">' +
|
|
69
|
+
'<p class="dialog-message">' + escapeHtml(message) + '</p>' +
|
|
70
|
+
'</div>' +
|
|
71
|
+
'<div class="dialog-footer">' +
|
|
72
|
+
'<button class="dialog-btn dialog-btn-primary" data-action="ok">OK</button>' +
|
|
73
|
+
'</div>' +
|
|
74
|
+
'</div>';
|
|
75
|
+
|
|
76
|
+
var okBtn = dialog.querySelector('[data-action="ok"]');
|
|
77
|
+
okBtn.addEventListener('click', function() {
|
|
78
|
+
closeDialog(dialog, overlay);
|
|
79
|
+
resolve(true);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
overlay.querySelector('.dialog-backdrop').addEventListener('click', function() {
|
|
83
|
+
closeDialog(dialog, overlay);
|
|
84
|
+
resolve(true);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
document.addEventListener('keydown', function handler(e) {
|
|
88
|
+
if (e.key === 'Escape' || e.key === 'Enter') {
|
|
89
|
+
document.removeEventListener('keydown', handler);
|
|
90
|
+
closeDialog(dialog, overlay);
|
|
91
|
+
resolve(true);
|
|
92
|
+
}
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
showDialog(dialog, overlay);
|
|
96
|
+
});
|
|
97
|
+
},
|
|
98
|
+
|
|
99
|
+
confirm: function(message, title) {
|
|
100
|
+
return new Promise(function(resolve) {
|
|
101
|
+
var overlay = createOverlay();
|
|
102
|
+
var dialog = document.createElement('div');
|
|
103
|
+
dialog.className = 'dialog-container';
|
|
104
|
+
dialog.innerHTML =
|
|
105
|
+
'<div class="dialog-box">' +
|
|
106
|
+
'<div class="dialog-header">' +
|
|
107
|
+
'<h3 class="dialog-title">' + escapeHtml(title || 'Confirm') + '</h3>' +
|
|
108
|
+
'</div>' +
|
|
109
|
+
'<div class="dialog-body">' +
|
|
110
|
+
'<p class="dialog-message">' + escapeHtml(message).replace(/\n/g, '<br>') + '</p>' +
|
|
111
|
+
'</div>' +
|
|
112
|
+
'<div class="dialog-footer">' +
|
|
113
|
+
'<button class="dialog-btn dialog-btn-secondary" data-action="cancel">Cancel</button>' +
|
|
114
|
+
'<button class="dialog-btn dialog-btn-primary dialog-btn-danger" data-action="confirm">Confirm</button>' +
|
|
115
|
+
'</div>' +
|
|
116
|
+
'</div>';
|
|
117
|
+
|
|
118
|
+
var cancelBtn = dialog.querySelector('[data-action="cancel"]');
|
|
119
|
+
var confirmBtn = dialog.querySelector('[data-action="confirm"]');
|
|
120
|
+
|
|
121
|
+
cancelBtn.addEventListener('click', function() {
|
|
122
|
+
closeDialog(dialog, overlay);
|
|
123
|
+
resolve(false);
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
confirmBtn.addEventListener('click', function() {
|
|
127
|
+
closeDialog(dialog, overlay);
|
|
128
|
+
resolve(true);
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
overlay.querySelector('.dialog-backdrop').addEventListener('click', function() {
|
|
132
|
+
closeDialog(dialog, overlay);
|
|
133
|
+
resolve(false);
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
document.addEventListener('keydown', function handler(e) {
|
|
137
|
+
if (e.key === 'Escape') {
|
|
138
|
+
document.removeEventListener('keydown', handler);
|
|
139
|
+
closeDialog(dialog, overlay);
|
|
140
|
+
resolve(false);
|
|
141
|
+
} else if (e.key === 'Enter') {
|
|
142
|
+
document.removeEventListener('keydown', handler);
|
|
143
|
+
closeDialog(dialog, overlay);
|
|
144
|
+
resolve(true);
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
showDialog(dialog, overlay);
|
|
149
|
+
});
|
|
150
|
+
},
|
|
151
|
+
|
|
152
|
+
prompt: function(message, defaultValue, title) {
|
|
153
|
+
return new Promise(function(resolve) {
|
|
154
|
+
var overlay = createOverlay();
|
|
155
|
+
var dialog = document.createElement('div');
|
|
156
|
+
dialog.className = 'dialog-container';
|
|
157
|
+
dialog.innerHTML =
|
|
158
|
+
'<div class="dialog-box">' +
|
|
159
|
+
'<div class="dialog-header">' +
|
|
160
|
+
'<h3 class="dialog-title">' + escapeHtml(title || 'Input') + '</h3>' +
|
|
161
|
+
'</div>' +
|
|
162
|
+
'<div class="dialog-body">' +
|
|
163
|
+
'<label class="dialog-label">' + escapeHtml(message) + '</label>' +
|
|
164
|
+
'<input type="text" class="dialog-input" value="' + escapeHtml(defaultValue || '') + '">' +
|
|
165
|
+
'</div>' +
|
|
166
|
+
'<div class="dialog-footer">' +
|
|
167
|
+
'<button class="dialog-btn dialog-btn-secondary" data-action="cancel">Cancel</button>' +
|
|
168
|
+
'<button class="dialog-btn dialog-btn-primary" data-action="ok">OK</button>' +
|
|
169
|
+
'</div>' +
|
|
170
|
+
'</div>';
|
|
171
|
+
|
|
172
|
+
var input = dialog.querySelector('.dialog-input');
|
|
173
|
+
var cancelBtn = dialog.querySelector('[data-action="cancel"]');
|
|
174
|
+
var okBtn = dialog.querySelector('[data-action="ok"]');
|
|
175
|
+
|
|
176
|
+
cancelBtn.addEventListener('click', function() {
|
|
177
|
+
closeDialog(dialog, overlay);
|
|
178
|
+
resolve(null);
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
okBtn.addEventListener('click', function() {
|
|
182
|
+
closeDialog(dialog, overlay);
|
|
183
|
+
resolve(input.value);
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
input.addEventListener('keydown', function(e) {
|
|
187
|
+
if (e.key === 'Enter') {
|
|
188
|
+
closeDialog(dialog, overlay);
|
|
189
|
+
resolve(input.value);
|
|
190
|
+
}
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
overlay.querySelector('.dialog-backdrop').addEventListener('click', function() {
|
|
194
|
+
closeDialog(dialog, overlay);
|
|
195
|
+
resolve(null);
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
document.addEventListener('keydown', function handler(e) {
|
|
199
|
+
if (e.key === 'Escape') {
|
|
200
|
+
document.removeEventListener('keydown', handler);
|
|
201
|
+
closeDialog(dialog, overlay);
|
|
202
|
+
resolve(null);
|
|
203
|
+
}
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
showDialog(dialog, overlay);
|
|
207
|
+
});
|
|
208
|
+
},
|
|
209
|
+
|
|
210
|
+
showProgress: function(config) {
|
|
211
|
+
var overlay = createOverlay();
|
|
212
|
+
var dialog = document.createElement('div');
|
|
213
|
+
dialog.className = 'dialog-container';
|
|
214
|
+
dialog.innerHTML =
|
|
215
|
+
'<div class="dialog-box dialog-box-progress">' +
|
|
216
|
+
'<div class="dialog-header">' +
|
|
217
|
+
'<h3 class="dialog-title">' + escapeHtml(config.title || 'Please wait') + '</h3>' +
|
|
218
|
+
'</div>' +
|
|
219
|
+
'<div class="dialog-body">' +
|
|
220
|
+
'<p class="dialog-message progress-message">' + escapeHtml(config.message || 'Loading...') + '</p>' +
|
|
221
|
+
'<div class="dialog-progress-bar">' +
|
|
222
|
+
'<div class="dialog-progress-fill" style="width: 0%"></div>' +
|
|
223
|
+
'</div>' +
|
|
224
|
+
'<p class="dialog-progress-percent">0%</p>' +
|
|
225
|
+
'</div>' +
|
|
226
|
+
'</div>';
|
|
227
|
+
|
|
228
|
+
showDialog(dialog, overlay);
|
|
229
|
+
|
|
230
|
+
var progressFill = dialog.querySelector('.dialog-progress-fill');
|
|
231
|
+
var progressPercent = dialog.querySelector('.dialog-progress-percent');
|
|
232
|
+
var progressMessage = dialog.querySelector('.progress-message');
|
|
233
|
+
|
|
234
|
+
return {
|
|
235
|
+
update: function(percent, message) {
|
|
236
|
+
progressFill.style.width = percent + '%';
|
|
237
|
+
progressPercent.textContent = Math.round(percent) + '%';
|
|
238
|
+
if (message) progressMessage.textContent = message;
|
|
239
|
+
},
|
|
240
|
+
close: function() {
|
|
241
|
+
closeDialog(dialog, overlay);
|
|
242
|
+
}
|
|
243
|
+
};
|
|
244
|
+
},
|
|
245
|
+
|
|
246
|
+
showToast: function(message, type, duration) {
|
|
247
|
+
var existing = document.querySelector('.toast-notification');
|
|
248
|
+
if (existing) existing.remove();
|
|
249
|
+
|
|
250
|
+
var toast = document.createElement('div');
|
|
251
|
+
toast.className = 'toast-notification toast-' + (type || 'info');
|
|
252
|
+
toast.innerHTML = '<span class="toast-message">' + escapeHtml(message) + '</span>';
|
|
253
|
+
document.body.appendChild(toast);
|
|
254
|
+
|
|
255
|
+
requestAnimationFrame(function() {
|
|
256
|
+
toast.classList.add('visible');
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
setTimeout(function() {
|
|
260
|
+
toast.classList.remove('visible');
|
|
261
|
+
setTimeout(function() { if (toast.parentNode) toast.remove(); }, 300);
|
|
262
|
+
}, duration || 3000);
|
|
263
|
+
},
|
|
264
|
+
|
|
265
|
+
closeAll: closeAllDialogs
|
|
266
|
+
};
|
|
267
|
+
})();
|
package/static/js/features.js
CHANGED
|
@@ -123,7 +123,7 @@
|
|
|
123
123
|
overlay.classList.remove('visible');
|
|
124
124
|
|
|
125
125
|
if (!currentConversation) {
|
|
126
|
-
showToast('Select a conversation first', 'error');
|
|
126
|
+
if (window.UIDialog) window.UIDialog.showToast('Select a conversation first', 'error');
|
|
127
127
|
return;
|
|
128
128
|
}
|
|
129
129
|
|
|
@@ -136,7 +136,7 @@
|
|
|
136
136
|
|
|
137
137
|
function uploadFiles(files) {
|
|
138
138
|
if (!currentConversation) {
|
|
139
|
-
showToast('No conversation selected', 'error');
|
|
139
|
+
if (window.UIDialog) window.UIDialog.showToast('No conversation selected', 'error');
|
|
140
140
|
return;
|
|
141
141
|
}
|
|
142
142
|
|
|
@@ -145,7 +145,7 @@
|
|
|
145
145
|
formData.append('file', files[i]);
|
|
146
146
|
}
|
|
147
147
|
|
|
148
|
-
showToast('Uploading ' + files.length + ' file(s)...', 'info');
|
|
148
|
+
if (window.UIDialog) window.UIDialog.showToast('Uploading ' + files.length + ' file(s)...', 'info');
|
|
149
149
|
|
|
150
150
|
fetch(BASE + '/api/upload/' + currentConversation, {
|
|
151
151
|
method: 'POST',
|
|
@@ -154,13 +154,13 @@
|
|
|
154
154
|
.then(function(res) { return res.json(); })
|
|
155
155
|
.then(function(data) {
|
|
156
156
|
if (data.ok) {
|
|
157
|
-
showToast(data.count + ' file(s) uploaded', 'success');
|
|
157
|
+
if (window.UIDialog) window.UIDialog.showToast(data.count + ' file(s) uploaded', 'success');
|
|
158
158
|
} else {
|
|
159
|
-
showToast('Upload failed: ' + (data.error || 'Unknown error'), 'error');
|
|
159
|
+
if (window.UIDialog) window.UIDialog.showToast('Upload failed: ' + (data.error || 'Unknown error'), 'error');
|
|
160
160
|
}
|
|
161
161
|
})
|
|
162
162
|
.catch(function(err) {
|
|
163
|
-
showToast('Upload failed: ' + err.message, 'error');
|
|
163
|
+
if (window.UIDialog) window.UIDialog.showToast('Upload failed: ' + err.message, 'error');
|
|
164
164
|
});
|
|
165
165
|
}
|
|
166
166
|
|
|
@@ -197,6 +197,42 @@
|
|
|
197
197
|
});
|
|
198
198
|
}
|
|
199
199
|
|
|
200
|
+
function showVoiceDownloadProgress() {
|
|
201
|
+
if (window._voiceProgressDialog) return;
|
|
202
|
+
|
|
203
|
+
window._voiceProgressDialog = window.UIDialog.showProgress({
|
|
204
|
+
title: 'Downloading Voice Models',
|
|
205
|
+
message: 'Preparing speech recognition and synthesis models...'
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
var checkInterval = setInterval(function() {
|
|
209
|
+
if (isVoiceReady()) {
|
|
210
|
+
clearInterval(checkInterval);
|
|
211
|
+
if (window._voiceProgressDialog) {
|
|
212
|
+
window._voiceProgressDialog.close();
|
|
213
|
+
window._voiceProgressDialog = null;
|
|
214
|
+
}
|
|
215
|
+
switchView('voice');
|
|
216
|
+
}
|
|
217
|
+
}, 500);
|
|
218
|
+
|
|
219
|
+
setTimeout(function() {
|
|
220
|
+
clearInterval(checkInterval);
|
|
221
|
+
if (window._voiceProgressDialog) {
|
|
222
|
+
window._voiceProgressDialog.close();
|
|
223
|
+
window._voiceProgressDialog = null;
|
|
224
|
+
}
|
|
225
|
+
}, 120000);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
function updateVoiceProgress(percent, message) {
|
|
229
|
+
if (window._voiceProgressDialog) {
|
|
230
|
+
window._voiceProgressDialog.update(percent, message);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
window.__updateVoiceProgress = updateVoiceProgress;
|
|
235
|
+
|
|
200
236
|
function isVoiceReady() {
|
|
201
237
|
var client = window.agentGUIClient;
|
|
202
238
|
if (!client) return false;
|
|
@@ -98,6 +98,14 @@ class StreamingRenderer {
|
|
|
98
98
|
* Setup scroll optimization and auto-scroll
|
|
99
99
|
*/
|
|
100
100
|
setupScrollOptimization() {
|
|
101
|
+
if (!this.scrollContainer) return;
|
|
102
|
+
this._userScrolledUp = false;
|
|
103
|
+
this.scrollContainer.addEventListener('scroll', () => {
|
|
104
|
+
if (this._programmaticScroll) return;
|
|
105
|
+
const sc = this.scrollContainer;
|
|
106
|
+
const distFromBottom = sc.scrollHeight - sc.scrollTop - sc.clientHeight;
|
|
107
|
+
this._userScrolledUp = distFromBottom > 80;
|
|
108
|
+
});
|
|
101
109
|
}
|
|
102
110
|
|
|
103
111
|
/**
|
|
@@ -749,7 +757,6 @@ class StreamingRenderer {
|
|
|
749
757
|
|
|
750
758
|
const details = document.createElement('details');
|
|
751
759
|
details.className = 'block-tool-use folded-tool';
|
|
752
|
-
details.setAttribute('open', '');
|
|
753
760
|
if (block.id) details.dataset.toolUseId = block.id;
|
|
754
761
|
const colorIndex = this._getBlockColorIndex('tool_use');
|
|
755
762
|
details.style.borderLeft = `3px solid var(--block-color-${colorIndex})`;
|
|
@@ -856,7 +863,7 @@ class StreamingRenderer {
|
|
|
856
863
|
/**
|
|
857
864
|
* Static HTML version of smart content rendering for use in string templates
|
|
858
865
|
*/
|
|
859
|
-
static renderSmartContentHTML(contentStr, escapeHtml) {
|
|
866
|
+
static renderSmartContentHTML(contentStr, escapeHtml, flat = false) {
|
|
860
867
|
const trimmed = contentStr.trim();
|
|
861
868
|
const esc = escapeHtml || window._escHtml;
|
|
862
869
|
|
|
@@ -874,8 +881,7 @@ class StreamingRenderer {
|
|
|
874
881
|
const textParts = parsed.filter(b => b.type === 'text' && b.text);
|
|
875
882
|
if (textParts.length > 0) {
|
|
876
883
|
const combined = textParts.map(b => b.text).join('\n');
|
|
877
|
-
|
|
878
|
-
return StreamingRenderer.renderSmartContentHTML(combined, esc);
|
|
884
|
+
return StreamingRenderer.renderSmartContentHTML(combined, esc, flat);
|
|
879
885
|
}
|
|
880
886
|
}
|
|
881
887
|
|
|
@@ -905,7 +911,7 @@ class StreamingRenderer {
|
|
|
905
911
|
const cleanedContent = cleanedLines.join('\n');
|
|
906
912
|
|
|
907
913
|
// Try to detect and highlight code based on content patterns
|
|
908
|
-
return StreamingRenderer.renderCodeWithHighlight(cleanedContent, esc);
|
|
914
|
+
return StreamingRenderer.renderCodeWithHighlight(cleanedContent, esc, flat);
|
|
909
915
|
}
|
|
910
916
|
|
|
911
917
|
// Check for system reminder tags and format them specially
|
|
@@ -1001,7 +1007,7 @@ class StreamingRenderer {
|
|
|
1001
1007
|
|
|
1002
1008
|
// Add highlighted code
|
|
1003
1009
|
if (codeContent.trim()) {
|
|
1004
|
-
html += StreamingRenderer.renderCodeWithHighlight(codeContent, esc);
|
|
1010
|
+
html += StreamingRenderer.renderCodeWithHighlight(codeContent, esc, flat);
|
|
1005
1011
|
}
|
|
1006
1012
|
|
|
1007
1013
|
// Add system reminders if any
|
|
@@ -1021,7 +1027,7 @@ class StreamingRenderer {
|
|
|
1021
1027
|
if (contentWithoutReminders) {
|
|
1022
1028
|
// Check if remaining content looks like code
|
|
1023
1029
|
if (StreamingRenderer.detectCodeContent(contentWithoutReminders)) {
|
|
1024
|
-
html += StreamingRenderer.renderCodeWithHighlight(contentWithoutReminders, esc);
|
|
1030
|
+
html += StreamingRenderer.renderCodeWithHighlight(contentWithoutReminders, esc, flat);
|
|
1025
1031
|
} else {
|
|
1026
1032
|
html += `<pre class="tool-result-pre">${esc(contentWithoutReminders)}</pre>`;
|
|
1027
1033
|
}
|
|
@@ -1050,7 +1056,7 @@ class StreamingRenderer {
|
|
|
1050
1056
|
// Check if this looks like code
|
|
1051
1057
|
const looksLikeCode = StreamingRenderer.detectCodeContent(trimmed);
|
|
1052
1058
|
if (looksLikeCode) {
|
|
1053
|
-
return StreamingRenderer.renderCodeWithHighlight(trimmed, esc);
|
|
1059
|
+
return StreamingRenderer.renderCodeWithHighlight(trimmed, esc, flat);
|
|
1054
1060
|
}
|
|
1055
1061
|
|
|
1056
1062
|
const displayContent = trimmed.length > 2000 ? trimmed.substring(0, 2000) + '\n... (truncated)' : trimmed;
|
|
@@ -1109,11 +1115,12 @@ class StreamingRenderer {
|
|
|
1109
1115
|
/**
|
|
1110
1116
|
* Render code with basic syntax highlighting
|
|
1111
1117
|
*/
|
|
1112
|
-
static renderCodeWithHighlight(code, esc) {
|
|
1113
|
-
const preStyle = "background:#1e293b;padding:1rem;border-radius:0
|
|
1118
|
+
static renderCodeWithHighlight(code, esc, flat = false) {
|
|
1119
|
+
const preStyle = "background:#1e293b;padding:1rem;border-radius:0.375rem;overflow-x:auto;font-family:'Monaco','Menlo','Ubuntu Mono',monospace;font-size:0.875rem;line-height:1.6;color:#e2e8f0;border:1px solid #334155;margin:0";
|
|
1120
|
+
const codeHtml = `<pre style="${preStyle}"><code class="lazy-hl">${esc(code)}</code></pre>`;
|
|
1121
|
+
if (flat) return codeHtml;
|
|
1114
1122
|
const lineCount = code.split('\n').length;
|
|
1115
1123
|
const summaryLabel = `code - ${lineCount} line${lineCount !== 1 ? 's' : ''}`;
|
|
1116
|
-
const codeHtml = `<pre style="${preStyle}"><code class="lazy-hl">${esc(code)}</code></pre>`;
|
|
1117
1124
|
return `<details class="collapsible-code"><summary class="collapsible-code-summary">${summaryLabel}</summary>${codeHtml}</details>`;
|
|
1118
1125
|
}
|
|
1119
1126
|
|
|
@@ -1240,7 +1247,7 @@ class StreamingRenderer {
|
|
|
1240
1247
|
`;
|
|
1241
1248
|
wrapper.appendChild(header);
|
|
1242
1249
|
|
|
1243
|
-
const renderedContent = StreamingRenderer.renderSmartContentHTML(contentStr, this.escapeHtml.bind(this));
|
|
1250
|
+
const renderedContent = StreamingRenderer.renderSmartContentHTML(contentStr, this.escapeHtml.bind(this), true);
|
|
1244
1251
|
const body = document.createElement('div');
|
|
1245
1252
|
body.className = 'folded-tool-body';
|
|
1246
1253
|
if (!parentIsOpen) {
|
|
@@ -1348,7 +1355,6 @@ class StreamingRenderer {
|
|
|
1348
1355
|
const details = document.createElement('details');
|
|
1349
1356
|
details.className = isError ? 'folded-tool folded-tool-error' : 'folded-tool';
|
|
1350
1357
|
details.dataset.eventType = 'result';
|
|
1351
|
-
if (!isError) details.setAttribute('open', '');
|
|
1352
1358
|
const colorIndex = this._getBlockColorIndex(isError ? 'error' : 'result');
|
|
1353
1359
|
details.style.borderLeft = `3px solid var(--block-color-${colorIndex})`;
|
|
1354
1360
|
|
|
@@ -2003,16 +2009,22 @@ class StreamingRenderer {
|
|
|
2003
2009
|
* Auto-scroll to bottom of container
|
|
2004
2010
|
*/
|
|
2005
2011
|
autoScroll() {
|
|
2006
|
-
if (this._scrollRafPending) return;
|
|
2012
|
+
if (this._scrollRafPending || this._userScrolledUp) return;
|
|
2007
2013
|
this._scrollRafPending = true;
|
|
2008
2014
|
requestAnimationFrame(() => {
|
|
2009
2015
|
this._scrollRafPending = false;
|
|
2010
2016
|
if (this.scrollContainer) {
|
|
2017
|
+
this._programmaticScroll = true;
|
|
2011
2018
|
try { this.scrollContainer.scrollTop = this.scrollContainer.scrollHeight; } catch (_) {}
|
|
2019
|
+
this._programmaticScroll = false;
|
|
2012
2020
|
}
|
|
2013
2021
|
});
|
|
2014
2022
|
}
|
|
2015
2023
|
|
|
2024
|
+
resetScrollState() {
|
|
2025
|
+
this._userScrolledUp = false;
|
|
2026
|
+
}
|
|
2027
|
+
|
|
2016
2028
|
updateVirtualScroll() {
|
|
2017
2029
|
}
|
|
2018
2030
|
|