diff options
| author | Agando <roduccomm@gmail.com> | 2026-06-23 06:00:45 +0200 |
|---|---|---|
| committer | Agando <roduccomm@gmail.com> | 2026-06-23 06:00:45 +0200 |
| commit | e9b75cd3a801689f57bc8a013f14c058e57e3b46 (patch) | |
| tree | a40a2b9ab142e4f3432d4686f52d177535b51d74 /modules/core/core-bundle.js | |
| parent | 6f6161562c5f1028256f3163083b665719741646 (diff) | |
auto: 2026-06-23_06:00 changes
Diffstat (limited to 'modules/core/core-bundle.js')
| -rw-r--r-- | modules/core/core-bundle.js | 170 |
1 files changed, 146 insertions, 24 deletions
diff --git a/modules/core/core-bundle.js b/modules/core/core-bundle.js index 0f13438..175df87 100644 --- a/modules/core/core-bundle.js +++ b/modules/core/core-bundle.js @@ -4183,32 +4183,69 @@ Only output XML function calls for tool tasks. If no tool is needed, answer norm refreshSkillsList() { const list = document.getElementById('skills-list'); list.innerHTML = ''; + const statsEl = document.getElementById('skills-stats'); const fs = this.fs; fs.mkdirRecursive('/skills'); const index = fs.fsReadJSON('/skills/index.json') || {}; const names = Object.keys(index); - if (names.length === 0) { - list.innerHTML = '<div style="padding:20px; text-align:center; color:#666">No skills yet. Create one below!</div>'; + // Search filter + const query = (document.getElementById('skills-search-input')?.value || '').toLowerCase().trim(); + let filtered = names; + if (query) { + filtered = names.filter(n => { + const s = index[n]; + return n.includes(query) || (s.description || '').toLowerCase().includes(query) || (s.tags || []).some(t => t.toLowerCase().includes(query)) || (s.category || '').toLowerCase().includes(query); + }); + } + // Stats + if (statsEl) { + const cats = new Set(Object.values(index).map(s => s.category || 'general')); + statsEl.innerHTML = `<span>๐ ${names.length} skills</span><span>๐ ${cats.size} categories</span>${query ? `<span>๐ ${filtered.length} match '${query}'</span>` : ''}`; + } + if (filtered.length === 0) { + list.innerHTML = '<div style="padding:20px; text-align:center; color:#666">' + (query ? 'No skills match your search.' : 'No skills yet. Create one below!') + '</div>'; return; } - names.forEach(name => { + // Group by category + const groups = {}; + for (const name of filtered) { const s = index[name]; - const tags = (s.tags || []).join(', '); - const div = document.createElement('div'); - div.className = 'module-item'; - div.innerHTML = ` - <div> - <strong style="color:var(--success-color)">${name}</strong> - <div style="font-size:0.85em; color:#aaa">${s.description || ''}</div> - <div style="font-size:0.75em; color:#666">${s.category || 'general'}${tags ? ' ยท ' + tags : ''}</div> - </div> - <div style="display:flex;gap:4px"> - <button class="btn" onclick="webx11.viewSkillUI('${name}')" style="padding:4px 8px; font-size:0.8em">๐</button> - <button class="btn btn-danger" onclick="webx11.deleteSkillUI('${name}')" style="padding:4px 8px; font-size:0.8em">โ</button> - </div> - `; - list.appendChild(div); - }); + const cat = s.category || 'general'; + if (!groups[cat]) groups[cat] = []; + groups[cat].push(name); + } + const catOrder = Object.keys(groups).sort(); + for (const cat of catOrder) { + const section = document.createElement('div'); + section.style.marginBottom = '6px'; + const header = document.createElement('div'); + header.style.cssText = 'font-size:0.8em; font-weight:600; color:var(--info-color); padding:4px 0; cursor:pointer; display:flex; align-items:center; gap:4px;'; + header.textContent = '๐ ' + cat + ' (' + groups[cat].length + ')'; + header.onclick = () => { const body = header.nextElementSibling; if (body) body.style.display = body.style.display === 'none' ? '' : 'none'; }; + section.appendChild(header); + const body = document.createElement('div'); + for (const name of groups[cat]) { + const s = index[name]; + const tags = (s.tags || []).join(', '); + const div = document.createElement('div'); + div.className = 'module-item'; + div.innerHTML = ` + <div> + <strong style="color:var(--success-color)">${name}</strong> + <div style="font-size:0.85em; color:#aaa">${s.description || ''}</div> + <div style="font-size:0.75em; color:#666">${tags ? '๐ท ' + tags : ''}</div> + </div> + <div style="display:flex;gap:4px"> + <button class="btn" onclick="webx11.viewSkillUI('${name}')" style="padding:4px 8px; font-size:0.8em">๐</button> + <button class="btn" onclick="webx11.editSkillUI('${name}')" style="padding:4px 8px; font-size:0.8em">โ๏ธ</button> + <button class="btn btn-danger" onclick="webx11.deleteSkillUI('${name}')" style="padding:4px 8px; font-size:0.8em">โ</button> + </div> + `; + body.appendChild(div); + } + section.appendChild(body); + list.appendChild(section); + } } viewSkillUI(name) { const fs = this.fs; @@ -4217,15 +4254,33 @@ Only output XML function calls for tool tasks. If no tool is needed, answer norm document.getElementById('skill-preview-title').textContent = name; document.getElementById('skill-preview-content').textContent = content; document.getElementById('skill-preview').style.display = 'flex'; + document.getElementById('skill-preview-expand-btn').textContent = 'โถ Expand'; + document.getElementById('skill-preview').style.maxHeight = '250px'; } closeSkillPreview() { document.getElementById('skill-preview').style.display = 'none'; } + toggleSkillPreviewSize() { + const preview = document.getElementById('skill-preview'); + const btn = document.getElementById('skill-preview-expand-btn'); + if (preview.style.maxHeight === 'none' || preview.style.maxHeight === '80vh') { + preview.style.maxHeight = '250px'; + btn.textContent = 'โถ Expand'; + } else { + preview.style.maxHeight = '80vh'; + btn.textContent = 'โถ Collapse'; + } + } + editSkillUI(name) { + this.closeSkillsManager(); + if (typeof webx11.dispatchCommand === 'function') { + setTimeout(() => webx11.dispatchCommand('edit', ['/skills/' + name + '.md']), 200); + } + } deleteSkillUI(name) { if (!confirm(`Delete skill "${name}"?`)) return; const fs = this.fs; fs.mkdirRecursive('/skills'); - // Actually remove from VFS tree if (fs.root && fs.root.content && fs.root.content['skills'] && fs.root.content['skills'].content) { delete fs.root.content['skills'].content[`${name}.md`]; } @@ -4239,6 +4294,7 @@ Only output XML function calls for tool tasks. If no tool is needed, answer norm createSkillFromUI() { const name = document.getElementById('skill-name-input').value.trim().replace(/\s+/g, '-').toLowerCase(); const description = document.getElementById('skill-desc-input').value.trim() || 'No description'; + const category = document.getElementById('skill-category-input')?.value || 'general'; const tags = document.getElementById('skill-tags-input').value.trim().split(',').map(t => t.trim()).filter(Boolean); const contentInput = document.getElementById('skill-content-input').value.trim(); if (!name) { alert('Skill name is required'); return; } @@ -4248,13 +4304,13 @@ Only output XML function calls for tool tasks. If no tool is needed, answer norm alert(`Skill '${name}' already exists. Delete it first or use skill-patch command.`); return; } - let content = contentInput || `---\nname: ${name}\ndescription: ${description}\ntags: [${tags.join(', ')}]\ncategory: general\n---\n## Steps\n1. (to be filled)\n`; + let content = contentInput || `---\nname: ${name}\ndescription: ${description}\ntags: [${tags.join(', ')}]\ncategory: ${category}\n---\n## Steps\n1. (to be filled)\n`; if (!content.startsWith('---')) { - content = `---\nname: ${name}\ndescription: ${description}\ntags: [${tags.join(', ')}]\ncategory: general\n---\n${content}`; + content = `---\nname: ${name}\ndescription: ${description}\ntags: [${tags.join(', ')}]\ncategory: ${category}\n---\n${content}`; } fs.writeFile(`/skills/${name}.md`, content); const index = fs.fsReadJSON('/skills/index.json') || {}; - index[name] = { name, description, category: 'general', tags }; + index[name] = { name, description, category, tags }; fs.fsWriteJSON('/skills/index.json', index); this.print(`Skill '${name}' created`, 'success'); document.getElementById('skill-name-input').value = ''; @@ -4263,6 +4319,70 @@ Only output XML function calls for tool tasks. If no tool is needed, answer norm document.getElementById('skill-content-input').value = ''; this.refreshSkillsList(); } + exportAllSkillsUI() { + const fs = this.fs; + const index = fs.fsReadJSON('/skills/index.json') || {}; + const names = Object.keys(index); + if (names.length === 0) { alert('No skills to export.'); return; } + const exportData = {}; + for (const name of names) { + const content = fs.readFile(`/skills/${name}.md`); + if (content !== null) { + exportData[name] = { ...index[name], content }; + } + } + const json = JSON.stringify(exportData, null, 2); + const blob = new Blob([json], { type: 'application/json' }); + const url = URL.createObjectURL(blob); + const a = document.createElement('a'); + a.href = url; + a.download = 'skills-export-' + new Date().toISOString().slice(0, 10) + '.json'; + a.click(); + URL.revokeObjectURL(url); + this.print(`๐ค Exported ${names.length} skills`, 'success'); + } + importSkillsUI(event) { + const file = event.target.files[0]; + if (!file) return; + const reader = new FileReader(); + reader.onload = (e) => { + try { + const data = JSON.parse(e.target.result); + const fs = this.fs; + fs.mkdirRecursive('/skills'); + const index = fs.fsReadJSON('/skills/index.json') || {}; + let imported = 0; + for (const [name, skill] of Object.entries(data)) { + if (!skill.content) continue; + fs.writeFile(`/skills/${name}.md`, skill.content); + index[name] = { + name, + description: skill.description || name, + tags: skill.tags || [], + category: skill.category || 'general', + created: (index[name] && index[name].created) || new Date().toISOString(), + updated: new Date().toISOString() + }; + imported++; + } + fs.fsWriteJSON('/skills/index.json', index); + this.print(`๐ฅ Imported ${imported} skills`, 'success'); + this.refreshSkillsList(); + } catch (err) { + alert('Invalid JSON file: ' + err.message); + } + }; + reader.readAsText(file); + event.target.value = ''; + } + syncSkillsToWebdavUI() { + const mod = this.moduleManager && this.moduleManager.modules['SkillPackCoreModule']; + if (mod && mod.commands && mod.commands['skill-webdav-sync']) { + mod.commands['skill-webdav-sync'](); + } else { + this.print('WebDAV sync not available. Is skill-pack-core loaded?', 'warning'); + } + } updateOllamaStatus() { const statusDiv = document.getElementById('ollama-status'); const statusText = document.getElementById('ollama-status-text'); @@ -4529,7 +4649,7 @@ Only output XML function calls for tool tasks. If no tool is needed, answer norm 'module-my.js','module-searxng.js','module-servermon.js','module-ollamamodels.js', 'module-config.js','module-simplegui.js','module-io.js','module-dynamicprompts.js', 'module-aiassist.js','module-aiasm.js','module-smartctx.js','module-duckduckgo.js', - 'module-fetcher.js','module-skills.js','module-chatstudio.js' + 'module-fetcher.js','module-chatstudio.js' ]; const extFiles = [ 'calc.js','voice.js','cron.js','theme.js','wpm.js','mindmap.js','webdav.js','skill-pack-core.js','editor.js','diff.js','alias.js','finder.js','filemgr.js','cryptogzip.js','tutorial.js','shell.js','busybox.js','floating.js','console.js','media.js' @@ -8104,6 +8224,8 @@ buildSystemPrompt() { } if (res.tool_calls && res.tool_calls.length > 0) { // v4.5.66: Loop detection โ break if AI repeats same tool+args 3+ times + // Reset per-turn so legitimate retries across turns don't false-positive + _seenCalls.clear(); let looping = false; for (const tc of res.tool_calls) { const sig = tc.function.name + ':' + (tc.function.arguments || ''); |