merge: upgrade to upstream v0.50.95 + keep custom additions
Upstream v0.50.95 features merged (Russian localization, slash commands, mic toggle fix, gateway sync fix, KaTeX/Prism.js, etc.) Custom additions preserved: - Tier-2 agent switching commands in commands.js - MC panel in index.html + MC CSS - _resolve_cli_toolsets() in config.py - Custom routes.py, server.py, boot.js, i18n.js, messages.js, workspace.js Files with conflict resolution (took upstream, custom code in other files): - CHANGELOG.md, config.py, commands.js, index.html, panels.js, style.css, ui.js
This commit is contained in:
@@ -200,6 +200,7 @@ async function openFile(path){
|
||||
$('previewPathText').textContent=path;
|
||||
$('previewArea').classList.add('visible');
|
||||
$('fileTree').style.display='none';
|
||||
const wsSearch=$('wsSearchWrap');if(wsSearch)wsSearch.style.display='none';
|
||||
|
||||
_previewCurrentPath = path;
|
||||
renderFileBreadcrumb(path);
|
||||
@@ -229,7 +230,27 @@ async function openFile(path){
|
||||
return;
|
||||
}
|
||||
showPreview('code');
|
||||
$('previewCode').textContent=data.content;
|
||||
// Apply syntax highlighting based on file extension
|
||||
const content = data.content || '';
|
||||
if(['yml','yaml'].includes(ext)){
|
||||
$('previewCode').className='preview-code hl-yaml';
|
||||
if(typeof highlightYAML==='function'){
|
||||
$('previewCode').innerHTML=highlightYAML(content);
|
||||
}else{
|
||||
$('previewCode').innerHTML=_highlightWithLineNumbers(content);
|
||||
}
|
||||
}else if(ext==='json'){
|
||||
$('previewCode').className='preview-code hl-json';
|
||||
$('previewCode').innerHTML=_highlightJSON(content);
|
||||
}else if(['py','js','ts','sh','bash','zsh','rb','go','rs','java','c','cpp','h','css','scss','html','xml','sql','r','lua','pl','php','swift','kt','dart'].includes(ext)){
|
||||
$('previewCode').className='preview-code';
|
||||
$('previewCode').textContent=content;
|
||||
requestAnimationFrame(()=>{if(typeof highlightCode==='function')highlightCode();});
|
||||
}else{
|
||||
// txt, toml, cfg, ini, conf, env, log, etc — readable with line numbers
|
||||
$('previewCode').className='preview-code hl-text';
|
||||
$('previewCode').innerHTML=_highlightWithLineNumbers(content);
|
||||
}
|
||||
}catch(e){
|
||||
// If it's a 400/too-large error, offer download instead
|
||||
downloadFile(path);
|
||||
@@ -287,3 +308,131 @@ function renderFileBreadcrumb(filePath) {
|
||||
bar.appendChild(seg);
|
||||
}
|
||||
}
|
||||
|
||||
// ── Syntax highlighting helpers for file preview ──
|
||||
function _escHtml(s){return s.replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"');}
|
||||
|
||||
function _highlightWithLineNumbers(text){
|
||||
return text.split('\n').map(line=>'<span class="code-line">'+_escHtml(line)+'</span>').join('\n');
|
||||
}
|
||||
|
||||
function _highlightJSON(text){
|
||||
try{
|
||||
const pretty=JSON.stringify(JSON.parse(text),null,2);
|
||||
return pretty.split('\n').map(raw=>{
|
||||
let line=_escHtml(raw);
|
||||
// Highlight keys
|
||||
line=line.replace(/^(\s*)(")([\w\s.\/\-_@:]+)(")(\s*:)/,'$1<span class="hl-key">$2$3$4</span><span class="hl-value">$5</span>');
|
||||
// Highlight string values (after colon)
|
||||
line=line.replace(/(:\s*)("[^&]*?")/g,'$1<span class="hl-string">$2</span>');
|
||||
// Highlight numbers
|
||||
line=line.replace(/:\s*(\d+\.?\d*)/g,': <span class="hl-number">$1</span>');
|
||||
// Highlight booleans / null
|
||||
line=line.replace(/:\s*(true|false|null)/g,': <span class="hl-bool">$1</span>');
|
||||
return '<span class="code-line">'+line+'</span>';
|
||||
}).join('\n');
|
||||
}catch(e){
|
||||
return _highlightWithLineNumbers(text);
|
||||
}
|
||||
}
|
||||
|
||||
// ── Workspace file search (server-side recursive) ──
|
||||
let _wsSearchTimer=null;
|
||||
function filterWsFiles(){
|
||||
// Debounce: wait 300ms after last keystroke
|
||||
clearTimeout(_wsSearchTimer);
|
||||
_wsSearchTimer=setTimeout(_doWsSearch,300);
|
||||
}
|
||||
async function _doWsSearch(){
|
||||
const input=$('wsSearchInput');
|
||||
const clearBtn=$('wsSearchClear');
|
||||
const tree=$('fileTree');
|
||||
if(!input||!tree)return;
|
||||
const query=input.value.trim().toLowerCase();
|
||||
if(clearBtn)clearBtn.classList.toggle('visible',query.length>0);
|
||||
// Remove any stale "no results" message
|
||||
const oldNoRes=tree.querySelector('.ws-no-results');
|
||||
if(oldNoRes)oldNoRes.remove();
|
||||
// If empty query, restore original file tree
|
||||
if(!query){
|
||||
if(typeof renderFileTree==='function')renderFileTree();
|
||||
return;
|
||||
}
|
||||
// Not searchable without a workspace
|
||||
if(!S.session||!S.session.workspace)return;
|
||||
// Show loading indicator
|
||||
tree.innerHTML='<div class="ws-no-results" style="opacity:.5">Suche...</div>';
|
||||
try{
|
||||
// Ask server to search recursively
|
||||
const data=await api(`/api/list?session_id=${encodeURIComponent(S.session.session_id)}&path=.&search=${encodeURIComponent(query)}`);
|
||||
const results=data.entries||[];
|
||||
if(!results.length){
|
||||
tree.innerHTML='<div class="ws-no-results">Keine Dateien gefunden</div>';
|
||||
return;
|
||||
}
|
||||
// Render flat result list with path info
|
||||
tree.innerHTML='';
|
||||
for(const item of results){
|
||||
const el=document.createElement('div');
|
||||
el.className='file-item';
|
||||
el.style.paddingLeft='10px';
|
||||
const iconEl=document.createElement('span');
|
||||
iconEl.className='file-icon';
|
||||
iconEl.innerHTML=fileIcon(item.name,item.type);
|
||||
el.appendChild(iconEl);
|
||||
const nameEl=document.createElement('span');
|
||||
nameEl.className='file-name';
|
||||
nameEl.textContent=item.name;
|
||||
el.appendChild(nameEl);
|
||||
// Show relative path as hint
|
||||
if(item.path){
|
||||
const pathEl=document.createElement('span');
|
||||
pathEl.className='file-size';
|
||||
pathEl.style.opacity='.4';
|
||||
const dir=item.path.substring(0,item.path.lastIndexOf('/'));
|
||||
pathEl.textContent=dir||'.';
|
||||
el.appendChild(pathEl);
|
||||
}
|
||||
if(item.type==='dir'){
|
||||
el.onclick=()=>{clearWsSearch();loadDir(item.path);};
|
||||
}else{
|
||||
el.onclick=()=>openFile(item.path);
|
||||
}
|
||||
tree.appendChild(el);
|
||||
}
|
||||
}catch(e){
|
||||
// Fallback: client-side filter on currently visible items
|
||||
tree.innerHTML='';
|
||||
const allItems=S.entries||[];
|
||||
const matches=allItems.filter(it=>it.name.toLowerCase().includes(query));
|
||||
if(!matches.length){
|
||||
tree.innerHTML='<div class="ws-no-results">Keine Dateien gefunden</div>';
|
||||
}else{
|
||||
for(const item of matches){
|
||||
const el=document.createElement('div');
|
||||
el.className='file-item';el.style.paddingLeft='10px';
|
||||
el.innerHTML='<span class="file-icon">'+fileIcon(item.name,item.type)+'</span><span class="file-name">'+item.name+'</span>';
|
||||
el.onclick=item.type==='dir'?()=>{clearWsSearch();loadDir(item.path);}:()=>openFile(item.path);
|
||||
tree.appendChild(el);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Toggle workspace search visibility
|
||||
function toggleWsSearch(){
|
||||
const wrap=$('wsSearchWrap');
|
||||
if(!wrap)return;
|
||||
// Show/hide the search bar
|
||||
wrap.style.display=wrap.style.display==='none'?'flex':'none';
|
||||
// Focus input when showing
|
||||
setTimeout(()=>{if(wrap.style.display!=='none'){const inp=$('wsSearchInput');if(inp)inp.focus();}},50);
|
||||
}
|
||||
|
||||
function clearWsSearch(){
|
||||
const input=$('wsSearchInput');
|
||||
if(input)input.value='';
|
||||
const clearBtn=$('wsSearchClear');
|
||||
if(clearBtn)clearBtn.classList.remove('visible');
|
||||
if(typeof renderFileTree==='function')renderFileTree();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user