From 5c444afe06737912f7958e40c4ebb9be48d53dde Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E5=85=AC=E6=98=8E?=
<83812544+Ed1s0nZ@users.noreply.github.com>
Date: Sun, 19 Apr 2026 01:13:31 +0800
Subject: [PATCH] Add files via upload
---
web/static/i18n/en-US.json | 31 +++-
web/static/i18n/zh-CN.json | 31 +++-
web/static/js/skills.js | 367 +++++++++++++++++++++++++++++++------
web/templates/index.html | 42 +++--
4 files changed, 386 insertions(+), 85 deletions(-)
diff --git a/web/static/i18n/en-US.json b/web/static/i18n/en-US.json
index 698726d7..d0e9733e 100644
--- a/web/static/i18n/en-US.json
+++ b/web/static/i18n/en-US.json
@@ -719,9 +719,18 @@
"pathLabel": "Path:",
"modTimeLabel": "Modified:",
"contentLabel": "Content:",
+ "cardVersion": "v{{version}}",
+ "cardScripts": "{{count}} script(s)",
+ "cardFiles": "{{count}} file(s)",
+ "versionLabel": "Version:",
+ "scriptsHeading": "Scripts:",
+ "summaryHint": "(summary — load full body if needed)",
+ "loadFullBody": "Load full body",
+ "loadFullFailed": "Failed to load full skill body",
"nameRequired": "Skill name is required",
"contentRequired": "Skill content is required",
- "nameInvalid": "Skill name can only contain letters, numbers, hyphens and underscores",
+ "nameInvalid": "Use lowercase letters, digits, and hyphens only (Agent Skills name rules)",
+ "descriptionRequired": "Description is required (written to SKILL.md front matter)",
"saveSuccess": "Skill updated",
"createdSuccess": "Skill created",
"deleteConfirm": "Are you sure you want to delete skill \"{{name}}\"? This cannot be undone.",
@@ -1470,12 +1479,24 @@
"editSkill": "Edit Skill",
"skillName": "Skill name",
"skillNamePlaceholder": "e.g. sql-injection-testing",
- "skillNameHint": "Letters, numbers, hyphens and underscores only",
+ "skillNameHint": "Lowercase letters, digits, hyphens (Agent Skills name)",
"description": "Description",
"descriptionPlaceholder": "Short description",
- "contentLabel": "Content (Markdown)",
- "contentPlaceholder": "Enter skill content in Markdown...",
- "contentHint": "YAML front matter supported (optional), e.g.:"
+ "descriptionHint": "Maps to the description field in SKILL.md YAML (when creating/editing SKILL.md)",
+ "packageFiles": "Package files",
+ "editingFile": "Editing",
+ "newFile": "New file",
+ "newFilePlaceholder": "Relative path, e.g. FORMS.md or scripts/extra.sh",
+ "newFilePathRequired": "Enter a path for the new file",
+ "newFilePathInvalid": "Invalid path (no .. or absolute paths)",
+ "noPackageFiles": "No files listed",
+ "unsavedSwitch": "You have unsaved changes. Switch file anyway?",
+ "contentLabel": "Content",
+ "contentPlaceholder": "Edit the selected file…",
+ "contentPlaceholderAdd": "SKILL.md body only (front matter is generated)…",
+ "bodyHintEdit": "For SKILL.md this is the body only (no --- header); save merges with name/description into standard SKILL.md.",
+ "contentHintAdd": "Creates standard SKILL.md (YAML front matter + body). Drop open-source skill folders into skills/ as-is.",
+ "contentHint": "See Claude Agent Skills format"
},
"knowledgeItemModal": {
"addKnowledge": "Add knowledge",
diff --git a/web/static/i18n/zh-CN.json b/web/static/i18n/zh-CN.json
index fcbc6d5b..08b4cf1c 100644
--- a/web/static/i18n/zh-CN.json
+++ b/web/static/i18n/zh-CN.json
@@ -719,9 +719,18 @@
"pathLabel": "路径:",
"modTimeLabel": "修改时间:",
"contentLabel": "内容:",
+ "cardVersion": "v{{version}}",
+ "cardScripts": "{{count}} 个脚本",
+ "cardFiles": "{{count}} 个文件",
+ "versionLabel": "版本:",
+ "scriptsHeading": "脚本:",
+ "summaryHint": "(摘要 — 可加载完整正文)",
+ "loadFullBody": "加载完整正文",
+ "loadFullFailed": "加载完整正文失败",
"nameRequired": "skill名称不能为空",
"contentRequired": "skill内容不能为空",
- "nameInvalid": "skill名称只能包含字母、数字、连字符和下划线",
+ "nameInvalid": "目录名须为小写字母、数字与连字符(与 Agent Skills 的 name 一致)",
+ "descriptionRequired": "描述不能为空(将写入 SKILL.md 的 front matter)",
"saveSuccess": "skill已更新",
"createdSuccess": "skill已创建",
"deleteConfirm": "确定要删除skill \"{{name}}\" 吗?此操作不可恢复。",
@@ -1470,12 +1479,24 @@
"editSkill": "编辑Skill",
"skillName": "Skill名称",
"skillNamePlaceholder": "例如: sql-injection-testing",
- "skillNameHint": "只能包含字母、数字、连字符和下划线",
+ "skillNameHint": "小写字母、数字、连字符(与 Agent Skills 的 name 一致)",
"description": "描述",
"descriptionPlaceholder": "Skill的简短描述",
- "contentLabel": "内容(Markdown格式)",
- "contentPlaceholder": "输入skill内容,支持Markdown格式...",
- "contentHint": "支持YAML front matter格式(可选),例如:"
+ "descriptionHint": "对应 SKILL.md 中 YAML 的 description 字段(创建/编辑 SKILL.md 时使用)",
+ "packageFiles": "包内文件",
+ "editingFile": "正在编辑",
+ "newFile": "新建文件",
+ "newFilePlaceholder": "新文件路径,如 FORMS.md 或 scripts/extra.sh",
+ "newFilePathRequired": "请填写新文件路径",
+ "newFilePathInvalid": "路径无效(禁止 .. 与绝对路径)",
+ "noPackageFiles": "未列出文件",
+ "unsavedSwitch": "当前文件有未保存修改,确定切换?",
+ "contentLabel": "内容",
+ "contentPlaceholder": "编辑当前选中的文件…",
+ "contentPlaceholderAdd": "SKILL.md 正文(无需手写 YAML 头)…",
+ "bodyHintEdit": "当前为 SKILL.md 的正文部分(不含 --- 头);保存时会合并名称/描述生成标准 SKILL.md。",
+ "contentHintAdd": "保存后生成标准 SKILL.md(YAML front matter + 正文)。开源技能目录可直接放入 skills/ 使用。",
+ "contentHint": "标准格式见 Claude Agent Skills 文档"
},
"knowledgeItemModal": {
"addKnowledge": "添加知识",
diff --git a/web/static/js/skills.js b/web/static/js/skills.js
index ca47719b..c8ea1456 100644
--- a/web/static/js/skills.js
+++ b/web/static/js/skills.js
@@ -4,6 +4,11 @@ function _t(key, opts) {
}
let skillsList = [];
let currentEditingSkillName = null;
+let skillModalAddMode = true;
+let skillActivePath = 'SKILL.md';
+let skillFileDirty = false;
+let skillPackageFiles = [];
+let skillModalControlsWired = false;
let isSavingSkill = false; // 防止重复提交
let skillsSearchKeyword = '';
let skillsSearchTimeout = null; // 搜索防抖定时器
@@ -154,20 +159,40 @@ function renderSkillsList() {
}
skillsListEl.innerHTML = filteredSkills.map(skill => {
+ const sid = skill.id || skill.name || '';
+ const ver = skill.version ? _t('skills.cardVersion', { version: skill.version }) : '';
+ const sc = typeof skill.script_count === 'number' && skill.script_count > 0
+ ? _t('skills.cardScripts', { count: skill.script_count })
+ : '';
+ const fc = typeof skill.file_count === 'number' && skill.file_count > 0
+ ? _t('skills.cardFiles', { count: skill.file_count })
+ : '';
+ const meta = [ver, fc, sc].filter(Boolean).join(' · ');
return `
-
-
-
+
+
+
`;
}).join('');
+
+ skillsListEl.querySelectorAll('[data-skill-view]').forEach(btn => {
+ btn.addEventListener('click', () => viewSkill(btn.getAttribute('data-skill-view')));
+ });
+ skillsListEl.querySelectorAll('[data-skill-edit]').forEach(btn => {
+ btn.addEventListener('click', () => editSkill(btn.getAttribute('data-skill-edit')));
+ });
+ skillsListEl.querySelectorAll('[data-skill-delete]').forEach(btn => {
+ btn.addEventListener('click', () => deleteSkill(btn.getAttribute('data-skill-delete')));
+ });
// 确保列表容器可以滚动,分页栏可见
// 使用 setTimeout 确保 DOM 更新完成后再检查
@@ -392,39 +417,174 @@ async function refreshSkills() {
}
// 显示添加skill模态框
+function wireSkillModalOnce() {
+ if (skillModalControlsWired) return;
+ skillModalControlsWired = true;
+ const addTa = document.getElementById('skill-content-add');
+ const edTa = document.getElementById('skill-content');
+ if (addTa) addTa.addEventListener('input', () => { if (skillModalAddMode) skillFileDirty = true; });
+ if (edTa) edTa.addEventListener('input', () => { if (!skillModalAddMode) skillFileDirty = true; });
+ const nb = document.getElementById('skill-new-file-btn');
+ if (nb) {
+ nb.addEventListener('click', () => {
+ if (!currentEditingSkillName) return;
+ const inp = document.getElementById('skill-new-file-path');
+ const p = (inp && inp.value || '').trim();
+ if (!p) {
+ showNotification(_t('skillModal.newFilePathRequired'), 'error');
+ return;
+ }
+ if (p.includes('..') || p.startsWith('/')) {
+ showNotification(_t('skillModal.newFilePathInvalid'), 'error');
+ return;
+ }
+ selectSkillPackageFile(currentEditingSkillName, p, { force: true, freshContent: '' });
+ if (inp) inp.value = '';
+ });
+ }
+}
+
function showAddSkillModal() {
+ wireSkillModalOnce();
const modal = document.getElementById('skill-modal');
if (!modal) return;
+ skillModalAddMode = true;
+ skillFileDirty = false;
+ skillActivePath = 'SKILL.md';
+ skillPackageFiles = [];
+ const pkg = document.getElementById('skill-package-editor');
+ const addEd = document.getElementById('skill-add-editor');
+ if (pkg) pkg.style.display = 'none';
+ if (addEd) addEd.style.display = 'block';
+
document.getElementById('skill-modal-title').textContent = _t('skills.addSkill');
document.getElementById('skill-name').value = '';
document.getElementById('skill-name').disabled = false;
document.getElementById('skill-description').value = '';
- document.getElementById('skill-content').value = '';
-
+ const addTa = document.getElementById('skill-content-add');
+ if (addTa) addTa.value = '';
+
modal.style.display = 'flex';
}
-// 编辑skill
-async function editSkill(skillName) {
+function renderSkillPackageTree() {
+ const el = document.getElementById('skill-package-tree');
+ if (!el) return;
+ const rows = (skillPackageFiles || []).filter(f => f.path && f.path !== '.').sort((a, b) =>
+ String(a.path).localeCompare(String(b.path)));
+ if (rows.length === 0) {
+ el.innerHTML = '' + escapeHtml(_t('skillModal.noPackageFiles')) + '
';
+ return;
+ }
+ el.innerHTML = rows.map(f => {
+ const path = f.path || '';
+ if (f.is_dir) {
+ return `${escapeHtml(path)}/
`;
+ }
+ const sel = path === skillActivePath
+ ? 'font-weight:600;background:rgba(99,102,241,0.12);'
+ : '';
+ return `${escapeHtml(path)}
`;
+ }).join('');
+ el.querySelectorAll('[data-skill-tree-path]').forEach(node => {
+ node.addEventListener('click', () => {
+ const p = node.getAttribute('data-skill-tree-path');
+ if (p) selectSkillPackageFile(currentEditingSkillName, p, {});
+ });
+ });
+}
+
+async function selectSkillPackageFile(skillId, path, opts) {
+ const force = opts && opts.force;
+ const freshContent = opts && Object.prototype.hasOwnProperty.call(opts, 'freshContent')
+ ? opts.freshContent
+ : null;
+ if (!force && skillFileDirty) {
+ if (!confirm(_t('skillModal.unsavedSwitch'))) {
+ return;
+ }
+ }
+ skillActivePath = path;
+ const label = document.getElementById('skill-active-path');
+ if (label) label.textContent = path;
+ const hint = document.getElementById('skill-body-hint-edit');
+ if (hint) hint.style.display = path === 'SKILL.md' ? 'block' : 'none';
+ const ta = document.getElementById('skill-content');
+ if (!ta) return;
+
+ if (freshContent !== null) {
+ ta.value = freshContent;
+ skillFileDirty = true;
+ renderSkillPackageTree();
+ return;
+ }
+
try {
- const response = await apiFetch(`/api/skills/${encodeURIComponent(skillName)}`);
- if (!response.ok) {
+ if (path === 'SKILL.md') {
+ const response = await apiFetch(`/api/skills/${encodeURIComponent(skillId)}?depth=full`);
+ if (!response.ok) throw new Error(_t('skills.loadDetailFailed'));
+ const data = await response.json();
+ const skill = data.skill;
+ ta.value = skill && skill.content != null ? skill.content : '';
+ } else {
+ const response = await apiFetch(`/api/skills/${encodeURIComponent(skillId)}/file?path=${encodeURIComponent(path)}`);
+ if (!response.ok) throw new Error(_t('skills.loadDetailFailed'));
+ const data = await response.json();
+ ta.value = data.content != null ? data.content : '';
+ }
+ skillFileDirty = false;
+ renderSkillPackageTree();
+ } catch (e) {
+ console.error(e);
+ showNotification(_t('skills.loadDetailFailed') + ': ' + e.message, 'error');
+ }
+}
+
+// 编辑skill
+async function editSkill(skillId) {
+ wireSkillModalOnce();
+ try {
+ const [detailRes, filesRes] = await Promise.all([
+ apiFetch(`/api/skills/${encodeURIComponent(skillId)}?depth=full`),
+ apiFetch(`/api/skills/${encodeURIComponent(skillId)}/files`)
+ ]);
+ if (!detailRes.ok) {
throw new Error(_t('skills.loadDetailFailed'));
}
- const data = await response.json();
+ const data = await detailRes.json();
const skill = data.skill;
const modal = document.getElementById('skill-modal');
if (!modal) return;
+ skillModalAddMode = false;
+ skillFileDirty = false;
+ skillActivePath = 'SKILL.md';
+ const pkg = document.getElementById('skill-package-editor');
+ const addEd = document.getElementById('skill-add-editor');
+ if (pkg) pkg.style.display = 'block';
+ if (addEd) addEd.style.display = 'none';
+
document.getElementById('skill-modal-title').textContent = _t('skills.editSkill');
- document.getElementById('skill-name').value = skill.name;
- document.getElementById('skill-name').disabled = true; // 编辑时不允许修改名称
+ document.getElementById('skill-name').value = skill.id || skillId;
+ document.getElementById('skill-name').disabled = true;
document.getElementById('skill-description').value = skill.description || '';
- document.getElementById('skill-content').value = skill.content || '';
-
- currentEditingSkillName = skillName;
+
+ if (filesRes.ok) {
+ const fd = await filesRes.json();
+ skillPackageFiles = fd.files || [];
+ } else {
+ skillPackageFiles = [];
+ }
+ renderSkillPackageTree();
+
+ const ta = document.getElementById('skill-content');
+ if (ta) ta.value = skill.content || '';
+ const hint = document.getElementById('skill-body-hint-edit');
+ if (hint) hint.style.display = 'block';
+
+ currentEditingSkillName = skillId;
modal.style.display = 'flex';
} catch (error) {
console.error('加载skill详情失败:', error);
@@ -432,48 +592,86 @@ async function editSkill(skillName) {
}
}
-// 查看skill
-async function viewSkill(skillName) {
+// 查看 skill:先摘要再按需拉全文(与多代理 Eino skill 渐进披露思路一致)
+async function viewSkill(skillId) {
try {
- const response = await apiFetch(`/api/skills/${encodeURIComponent(skillName)}`);
- if (!response.ok) {
+ const sumRes = await apiFetch(`/api/skills/${encodeURIComponent(skillId)}?depth=summary`);
+ if (!sumRes.ok) {
throw new Error(_t('skills.loadDetailFailed'));
}
- const data = await response.json();
- const skill = data.skill;
+ const sumData = await sumRes.json();
+ const sumSkill = sumData.skill;
- // 创建查看模态框
const modal = document.createElement('div');
modal.className = 'modal';
modal.id = 'skill-view-modal';
- const viewTitle = _t('skills.viewSkillTitle', { name: skill.name });
+ const viewTitle = _t('skills.viewSkillTitle', { name: sumSkill.name || skillId });
const descLabel = _t('skills.descriptionLabel');
const pathLabel = _t('skills.pathLabel');
const modTimeLabel = _t('skills.modTimeLabel');
const contentLabel = _t('skills.contentLabel');
const closeBtn = _t('common.close');
const editBtn = _t('common.edit');
+ const loadFullLabel = _t('skills.loadFullBody');
+ const scriptsLabel = _t('skills.scriptsHeading');
+
+ let scriptsBlock = '';
+ if (Array.isArray(sumSkill.scripts) && sumSkill.scripts.length > 0) {
+ const lines = sumSkill.scripts.map(s => {
+ const rel = escapeHtml(s.rel_path || s.RelPath || '');
+ const dn = escapeHtml(s.description || s.Description || '');
+ return `${rel}${dn ? ' — ' + dn : ''}`;
+ }).join('');
+ scriptsBlock = `${escapeHtml(scriptsLabel)} `;
+ }
+
modal.innerHTML = `
- ${skill.description ? `
${escapeHtml(descLabel)} ${escapeHtml(skill.description)}
` : ''}
-
${escapeHtml(pathLabel)} ${escapeHtml(skill.path || '')}
-
${escapeHtml(modTimeLabel)} ${escapeHtml(skill.mod_time || '')}
-
${escapeHtml(contentLabel)}
-
${escapeHtml(skill.content || '')}
+ ${sumSkill.version ? `
${escapeHtml(_t('skills.versionLabel'))} ${escapeHtml(sumSkill.version)}
` : ''}
+ ${sumSkill.description ? `
${escapeHtml(descLabel)} ${escapeHtml(sumSkill.description)}
` : ''}
+ ${scriptsBlock}
+
${escapeHtml(pathLabel)} ${escapeHtml(sumSkill.path || '')}
+
${escapeHtml(modTimeLabel)} ${escapeHtml(sumSkill.mod_time || '')}
+
${escapeHtml(contentLabel)} ${escapeHtml(_t('skills.summaryHint'))}
+
${escapeHtml(sumSkill.content || '')}
`;
document.body.appendChild(modal);
modal.style.display = 'flex';
+
+ const close = () => closeSkillViewModal();
+ modal.querySelectorAll('[data-skill-view-close]').forEach(el => el.addEventListener('click', close));
+ modal.querySelector('[data-skill-view-edit]').addEventListener('click', () => {
+ close();
+ editSkill(skillId);
+ });
+ modal.querySelector('[data-skill-load-full]').addEventListener('click', async () => {
+ const pre = modal.querySelector('#skill-view-body');
+ const btn = modal.querySelector('[data-skill-load-full]');
+ if (!pre || !btn) return;
+ btn.disabled = true;
+ try {
+ const fullRes = await apiFetch(`/api/skills/${encodeURIComponent(skillId)}?depth=full`);
+ if (!fullRes.ok) throw new Error(_t('skills.loadDetailFailed'));
+ const fullData = await fullRes.json();
+ pre.textContent = fullData.skill && fullData.skill.content != null ? fullData.skill.content : '';
+ } catch (e) {
+ showNotification(_t('skills.loadFullFailed') + ': ' + e.message, 'error');
+ } finally {
+ btn.disabled = false;
+ }
+ });
} catch (error) {
console.error('查看skill失败:', error);
showNotification(_t('skills.viewFailed') + ': ' + error.message, 'error');
@@ -494,6 +692,10 @@ function closeSkillModal() {
if (modal) {
modal.style.display = 'none';
currentEditingSkillName = null;
+ skillModalAddMode = true;
+ skillFileDirty = false;
+ skillPackageFiles = [];
+ skillActivePath = 'SKILL.md';
}
}
@@ -503,22 +705,28 @@ async function saveSkill() {
const name = document.getElementById('skill-name').value.trim();
const description = document.getElementById('skill-description').value.trim();
- const content = document.getElementById('skill-content').value.trim();
if (!name) {
showNotification(_t('skills.nameRequired'), 'error');
return;
}
- if (!content) {
- showNotification(_t('skills.contentRequired'), 'error');
+ if (!/^[a-z0-9]+(-[a-z0-9]+)*$/.test(name)) {
+ showNotification(_t('skills.nameInvalid'), 'error');
return;
}
- // 验证skill名称
- if (!/^[a-zA-Z0-9_-]+$/.test(name)) {
- showNotification(_t('skills.nameInvalid'), 'error');
- return;
+ if (skillModalAddMode || !currentEditingSkillName) {
+ if (!description) {
+ showNotification(_t('skills.descriptionRequired'), 'error');
+ return;
+ }
+ const content = (document.getElementById('skill-content-add') || {}).value;
+ const body = (content || '').trim();
+ if (!body) {
+ showNotification(_t('skills.contentRequired'), 'error');
+ return;
+ }
}
isSavingSkill = true;
@@ -529,29 +737,64 @@ async function saveSkill() {
}
try {
- const isEdit = !!currentEditingSkillName;
- const url = isEdit ? `/api/skills/${encodeURIComponent(currentEditingSkillName)}` : '/api/skills';
- const method = isEdit ? 'PUT' : 'POST';
-
- const response = await apiFetch(url, {
- method: method,
- headers: {
- 'Content-Type': 'application/json'
- },
- body: JSON.stringify({
- name: name,
- description: description,
- content: content
- })
- });
-
- if (!response.ok) {
- const error = await response.json();
- throw new Error(error.error || _t('skills.saveFailed'));
+ if (skillModalAddMode || !currentEditingSkillName) {
+ const content = (document.getElementById('skill-content-add') || {}).value;
+ const body = (content || '').trim();
+ const response = await apiFetch('/api/skills', {
+ method: 'POST',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ name, description, content: body })
+ });
+ if (!response.ok) {
+ const error = await response.json();
+ throw new Error(error.error || _t('skills.saveFailed'));
+ }
+ showNotification(_t('skills.createdSuccess'), 'success');
+ closeSkillModal();
+ await loadSkills(skillsPagination.currentPage, skillsPagination.pageSize);
+ return;
}
- showNotification(isEdit ? _t('skills.saveSuccess') : _t('skills.createdSuccess'), 'success');
- closeSkillModal();
+ const path = skillActivePath || 'SKILL.md';
+ const ta = document.getElementById('skill-content');
+ const raw = ta ? ta.value : '';
+ if (path === 'SKILL.md') {
+ if (!raw.trim()) {
+ showNotification(_t('skills.contentRequired'), 'error');
+ return;
+ }
+ const response = await apiFetch(`/api/skills/${encodeURIComponent(currentEditingSkillName)}`, {
+ method: 'PUT',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({
+ description: description,
+ content: raw.trim()
+ })
+ });
+ if (!response.ok) {
+ const error = await response.json();
+ throw new Error(error.error || _t('skills.saveFailed'));
+ }
+ } else {
+ const response = await apiFetch(`/api/skills/${encodeURIComponent(currentEditingSkillName)}/file`, {
+ method: 'PUT',
+ headers: { 'Content-Type': 'application/json' },
+ body: JSON.stringify({ path: path, content: raw })
+ });
+ if (!response.ok) {
+ const error = await response.json();
+ throw new Error(error.error || _t('skills.saveFailed'));
+ }
+ }
+
+ skillFileDirty = false;
+ showNotification(_t('skills.saveSuccess'), 'success');
+ const filesRes = await apiFetch(`/api/skills/${encodeURIComponent(currentEditingSkillName)}/files`);
+ if (filesRes.ok) {
+ const fd = await filesRes.json();
+ skillPackageFiles = fd.files || [];
+ renderSkillPackageTree();
+ }
await loadSkills(skillsPagination.currentPage, skillsPagination.pageSize);
} catch (error) {
console.error('保存skill失败:', error);
@@ -795,6 +1038,10 @@ document.addEventListener('languagechange', function () {
renderSkillsPagination();
}
}
+ const pkg = document.getElementById('skill-package-editor');
+ if (pkg && pkg.style.display !== 'none' && currentEditingSkillName) {
+ renderSkillPackageTree();
+ }
});
document.addEventListener('DOMContentLoaded', function () {
diff --git a/web/templates/index.html b/web/templates/index.html
index b9785450..fe23f33d 100644
--- a/web/templates/index.html
+++ b/web/templates/index.html
@@ -2114,7 +2114,7 @@