From c1bc4ac91d333cd7d2594b4102c900333a674ad7 Mon Sep 17 00:00:00 2001 From: "DESKTOP-72TV0V4\\caoxiaozhu" Date: Thu, 12 Mar 2026 16:42:48 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=9B=B4=E6=96=B0skill=E5=89=8D?= =?UTF-8?q?=E7=AB=AF=E9=A1=B5=E9=9D=A2=20-=20=E6=96=B0=E5=A2=9E=E7=BC=96?= =?UTF-8?q?=E8=BE=91=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - skill.ts: 新增编辑弹窗第二步、获取skill内容功能 - Skill.vue: 更新skill编辑界面 Co-Authored-By: Claude Opus 4.6 --- web/src/views/Skill.vue | 99 +++++++++++++------- web/src/views/skill/skill.ts | 170 ++++++++++++++++++++++++++++------- 2 files changed, 205 insertions(+), 64 deletions(-) diff --git a/web/src/views/Skill.vue b/web/src/views/Skill.vue index 29358fe..bf23ce9 100644 --- a/web/src/views/Skill.vue +++ b/web/src/views/Skill.vue @@ -9,11 +9,13 @@ const { searchQuery, filterStatus, isEditing, + isEditingStep2, isCreating, isEditingContent, editForm, newSkillForm, newSkillContent, + editSkillContent, filteredSkills, fetchSkills, openCreate, @@ -23,21 +25,20 @@ const { saveNewSkill, openEdit, closeEdit, - saveEdit, + goToEditStep2, + saveEditStep2, + closeEditStep2, toggleStatus, deleteSkill, - // 导入相关 - 从 useSkills 引入 - fileInputRef, + // 导入相关 isImporting, isImportingDialog, - importFileName, importSkillName, importSkillDesc, importSkillContent, isImportStep2, openImportDialog, closeImportDialog, - handleFileChange, submitImport, handleFolderSelect, // 下拉菜单 @@ -215,7 +216,58 @@ onMounted(() => {
- + +
+ + + + + + + @@ -319,7 +371,7 @@ onMounted(() => {

Import Skill

-

Select a file or folder to import

+

Select a folder containing SKILL.md

-
- -
- -

Import File

-

Select a single SKILL.md file

-
+
+ - - -
diff --git a/web/src/views/skill/skill.ts b/web/src/views/skill/skill.ts index 3c40c6b..a04f766 100644 --- a/web/src/views/skill/skill.ts +++ b/web/src/views/skill/skill.ts @@ -40,8 +40,9 @@ export function useSkills() { // 编辑状态 const isEditing = ref(false) + const isEditingContent = ref(false) // 创建弹窗的第二步 + const isEditingStep2 = ref(false) // 编辑弹窗的第二步 const isCreating = ref(false) - const isEditingContent = ref(false) const editingSkill = ref(null) // 表单 @@ -58,6 +59,7 @@ export function useSkills() { }) const newSkillContent = ref('') + const editSkillContent = ref('') // 编辑弹窗第二步的 SKILL.md 内容 // ============ 方法 ============ @@ -206,38 +208,152 @@ ${newSkillForm.value.skill_desc} } } - // 打开编辑弹窗 - const openEdit = (skill: Skill) => { + // 打开编辑弹窗(第一步) + const openEdit = async (skill: Skill) => { editingSkill.value = skill editForm.value = { skill_name: skill.skill_name, skill_desc: skill.skill_desc, skill_type: skill.skill_type, } + + // 读取现有的 SKILL.md 内容 + console.log('Opening edit for skill:', skill) + try { + const response = await fetch(`${API_BASE}/skill/content?id=${skill.id}`) + console.log('Content API response:', response.status, response.statusText) + if (response.ok) { + const text = await response.text() + console.log('Skill content:', text.substring(0, 100)) + editSkillContent.value = text + } else { + // API 返回 404 说明文件不存在,生成默认内容 + console.log('File not found, using default content') + editSkillContent.value = `--- +name: ${skill.skill_name} +description: ${skill.skill_desc || 'A custom skill'} +category: ${skill.skill_type || 'user'} +--- + +# ${skill.skill_name} + +${skill.skill_desc || 'A custom skill'} + +## Instructions + +Describe what this skill does and how it works. + +## Tools + +List the tools this skill can use: + +- + +## Examples + +Provide some examples of how to use this skill: + +\`\`\` +Example 1: +\`\`\` +` + } + } catch (error) { + console.error('Error getting skill content:', error) + // 网络错误也使用默认内容 + editSkillContent.value = `--- +name: ${skill.skill_name} +description: ${skill.skill_desc || 'A custom skill'} +category: ${skill.skill_type || 'user'} +--- + +# ${skill.skill_name} + +${skill.skill_desc || 'A custom skill'} + +## Instructions + +Describe what this skill does and how it works. + +## Tools + +List the tools this skill can use: + +- + +## Examples + +Provide some examples of how to use this skill: + +\`\`\` +Example 1: +\`\`\` +` + } + isEditing.value = true } // 关闭编辑弹窗 const closeEdit = () => { isEditing.value = false + isEditingStep2.value = false editingSkill.value = null + editSkillContent.value = '' } - // 保存编辑 - const saveEdit = async () => { + // 编辑第一步:点击 Next,跳转到编辑内容 + const goToEditStep2 = () => { + if (!editForm.value.skill_name || !editForm.value.skill_desc) { + ElMessage.warning('Please fill in skill name and description') + return + } + isEditing.value = false + isEditingStep2.value = true + } + + // 编辑第二步:保存 + const saveEditStep2 = async () => { + if (!editingSkill.value) return + try { - await updateSkill(editingSkill.value!.id, { - skill_name: editForm.value.skill_name, - skill_desc: editForm.value.skill_desc, - skill_type: editForm.value.skill_type, + // 将内容转换为文件上传 + const blob = new Blob([editSkillContent.value], { type: 'text/markdown' }) + const file = new File([blob], 'SKILL.md', { type: 'text/markdown' }) + + const formData = new FormData() + formData.append('skill_name', editForm.value.skill_name) + formData.append('skill_desc', editForm.value.skill_desc) + formData.append('skill_type', editForm.value.skill_type) + formData.append('file', file) + + const response = await fetch(`${API_BASE}/skill/${editingSkill.value.id}`, { + method: 'PUT', + body: formData, }) - ElMessage.success('Skill updated successfully') - isEditing.value = false + + if (response.ok) { + ElMessage.success('Skill updated successfully') + await fetchSkills() + isEditingStep2.value = false + editingSkill.value = null + editSkillContent.value = '' + } else { + const data = await response.json() + ElMessage.error(data.error || 'Failed to update skill') + } } catch (error) { + console.error('Failed to update skill:', error) ElMessage.error('Failed to update skill') } } + // 关闭编辑第二步弹窗 + const closeEditStep2 = () => { + isEditingStep2.value = false + isEditing.value = true + } + // 切换状态 const toggleStatus = async (skill: Skill) => { const newStatus = skill.status === 'active' ? 'inactive' : 'active' @@ -384,7 +500,7 @@ ${newSkillForm.value.skill_desc} } } - // 处理文件夹选择 + // 处理文件选择(单个 SKILL.md 文件) const handleFolderSelect = async (event: Event) => { const input = event.target as HTMLInputElement const files = input.files @@ -394,25 +510,13 @@ ${newSkillForm.value.skill_desc} isImporting.value = true try { - const folder = files[0].webkitRelativePath?.split('/')[0] || files[0].name + const file = files[0] - // 查找 SKILL.md 文件 - let skillMdFile: File | null = null - for (let i = 0; i < files.length; i++) { - const file = files[i] - if (file.name === 'SKILL.md' || file.webkitRelativePath?.endsWith('/SKILL.md')) { - skillMdFile = file - break - } - } + // 读取文件内容 + const content = await file.text() + const fileName = file.name.replace('.md', '') - if (!skillMdFile) { - ElMessage.error('导入失败:所选文件夹中未找到 SKILL.md 文件') - return - } - - const content = await skillMdFile.text() - const { skillName, skillDesc } = parseSkillContent(content, folder) + const { skillName, skillDesc } = parseSkillContent(content, fileName) // 使用导入弹窗显示内容 importSkillName.value = skillName @@ -422,7 +526,7 @@ ${newSkillForm.value.skill_desc} isImportStep2.value = true } catch (error) { console.error('Import failed:', error) - ElMessage.error('导入失败,请检查文件夹格式是否正确') + ElMessage.error('导入失败,请检查文件格式是否正确') } finally { isImporting.value = false input.value = '' @@ -453,12 +557,14 @@ ${newSkillForm.value.skill_desc} searchQuery, filterStatus, isEditing, + isEditingStep2, isCreating, isEditingContent, editingSkill, editForm, newSkillForm, newSkillContent, + editSkillContent, // Computed filteredSkills, // Methods @@ -470,7 +576,9 @@ ${newSkillForm.value.skill_desc} saveNewSkill, openEdit, closeEdit, - saveEdit, + goToEditStep2, + saveEditStep2, + closeEditStep2, toggleStatus, deleteSkill: handleDeleteSkill, // 导入相关