Просмотр исходного кода

fix: 精简技能库导入入口并修复去AI味选中

Mochocyang 2 месяцев назад
Родитель
Сommit
8b2b686a68

+ 41 - 10
src/components/skill-library/unified-skill-library-view.spec.tsx

@@ -159,11 +159,13 @@ describe("UnifiedSkillLibraryView", () => {
   it("shows de-AI actions in the right header when de-AI skill tab is active", async () => {
     const { container, root } = await renderLibrary()
     const actions = container.querySelector<HTMLElement>('[data-testid="skill-library-header-actions"]')
+    const actionLabels = Array.from(actions?.querySelectorAll("button") ?? [])
+      .map((button) => button.textContent?.trim())
 
-    expect(actions?.textContent).toContain("新建技能")
-    expect(actions?.textContent).toContain("导入技能")
-    expect(actions?.textContent).toContain("导入文件")
-    expect(actions?.textContent).toContain("导入文件夹")
+    expect(actionLabels).toEqual(["新建技能", "导入"])
+    expect(actions?.textContent).not.toContain("导入技能")
+    expect(actions?.textContent).not.toContain("导入文件")
+    expect(actions?.textContent).not.toContain("导入文件夹")
     expect(actions?.textContent).not.toContain("新建 Skill")
     expect(actions?.textContent).not.toContain("导出当前")
 
@@ -179,11 +181,14 @@ describe("UnifiedSkillLibraryView", () => {
     await flushEffects()
 
     const actions = container.querySelector<HTMLElement>('[data-testid="skill-library-header-actions"]')
-    expect(actions?.textContent).toContain("新建 Skill")
-    expect(actions?.textContent).toContain("导入 Skill")
-    expect(actions?.textContent).toContain("导入文件")
-    expect(actions?.textContent).toContain("导入文件夹")
-    expect(actions?.textContent).toContain("导出当前")
+    const actionLabels = Array.from(actions?.querySelectorAll("button") ?? [])
+      .map((button) => button.textContent?.trim())
+
+    expect(actionLabels).toEqual(["新建 Skill", "导入"])
+    expect(actions?.textContent).not.toContain("导入 Skill")
+    expect(actions?.textContent).not.toContain("导入文件")
+    expect(actions?.textContent).not.toContain("导入文件夹")
+    expect(actions?.textContent).not.toContain("导出当前")
     expect(actions?.textContent).not.toContain("新建技能")
 
     cleanup(root, container)
@@ -223,7 +228,7 @@ describe("UnifiedSkillLibraryView", () => {
     const { container, root } = await renderLibrary()
 
     await act(async () => {
-      getButton(container, "导入文件")?.click()
+      getButton(container, "导入")?.click()
     })
     await flushEffects()
 
@@ -284,4 +289,30 @@ describe("UnifiedSkillLibraryView", () => {
 
     cleanup(root, container)
   })
+
+  it("marks a de-AI entry as current after switching from a writing entry", async () => {
+    const { container, root } = await renderLibrary()
+    const writingEntry = container.querySelector<HTMLElement>('[data-testid="unified-skill-entry-writing:skill:three"]')
+    const deAiEntry = container.querySelector<HTMLElement>('[data-testid="unified-skill-entry-de-ai:project:quiet"]')
+
+    await act(async () => {
+      writingEntry?.click()
+    })
+    await flushEffects()
+
+    expect(writingEntry?.getAttribute("aria-current")).toBe("true")
+
+    await act(async () => {
+      deAiEntry?.click()
+    })
+    await flushEffects()
+
+    expect(useWikiStore.getState().activeView).toBe("skillLibrary")
+    expect(useWikiStore.getState().selectedSkillLibrarySkillId).toBe("project:quiet")
+    expect(useWikiStore.getState().selectedWritingSkillLibrarySkillId).toBeNull()
+    expect(deAiEntry?.getAttribute("aria-current")).toBe("true")
+    expect(writingEntry?.getAttribute("aria-current")).toBeNull()
+
+    cleanup(root, container)
+  })
 })

+ 8 - 83
src/components/skill-library/unified-skill-library-view.tsx

@@ -1,6 +1,6 @@
 import { useEffect, useMemo, useState } from "react"
-import { open, save } from "@tauri-apps/plugin-dialog"
-import { readFile, writeFile } from "@/commands/fs"
+import { open } from "@tauri-apps/plugin-dialog"
+import { readFile } from "@/commands/fs"
 import { useWikiStore } from "@/stores/wiki-store"
 import {
   createBlankProjectDeAiSkill,
@@ -12,8 +12,6 @@ import {
 } from "@/lib/novel/de-ai-skill-library"
 import {
   createBlankWritingSkill,
-  exportSkillToJson,
-  importLinkedSkill,
   importSkillFromJson,
   importWritingSkill,
   loadUserSkillConfig,
@@ -168,7 +166,6 @@ function SkillLibraryHeaderActions({ activeTab }: { activeTab: "skillLibrary" |
   const project = useWikiStore((s) => s.project)
   const bumpDataVersion = useWikiStore((s) => s.bumpDataVersion)
   const setActiveView = useWikiStore((s) => s.setActiveView)
-  const selectedWritingSkillId = useWikiStore((s) => s.selectedWritingSkillLibrarySkillId)
   const setSelectedSkillId = useWikiStore((s) => s.setSelectedSkillLibrarySkillId)
   const setSelectedWritingSkillId = useWikiStore((s) => s.setSelectedWritingSkillLibrarySkillId)
   const [message, setMessage] = useState("")
@@ -240,30 +237,6 @@ function SkillLibraryHeaderActions({ activeTab }: { activeTab: "skillLibrary" |
     }
   }
 
-  async function handleImportDeAiSkillFolder() {
-    if (!project || saving) return
-    try {
-      const selected = await open({ multiple: false, directory: true })
-      if (!selected || typeof selected !== "string") return
-      const skillPath = `${selected.replace(/[\\/]+$/, "")}/SKILL.md`
-      const content = await readFile(skillPath)
-      const imported = importedDeAiSkillFromContent(skillPath, content)
-      if (!imported) {
-        setMessage("导入失败:文件夹中未找到有效的 SKILL.md")
-        return
-      }
-      const config = await loadDeAiSkillConfig(project.path)
-      const next = normalizeDeAiSkillConfig({
-        ...config,
-        defaultSkillId: imported.id,
-        projectSkills: [imported, ...config.projectSkills],
-      })
-      await persistDeAiConfig(next, imported.id)
-    } catch {
-      setMessage("导入失败:文件夹中未找到有效的 SKILL.md")
-    }
-  }
-
   async function handleCreateWritingSkill() {
     if (!project || saving) return
     const config = await loadUserSkillConfig(project.path)
@@ -303,47 +276,7 @@ function SkillLibraryHeaderActions({ activeTab }: { activeTab: "skillLibrary" |
       }
       await persistWritingConfig(next, next.selectedSkillId)
     } catch {
-      setMessage("导入 Skill 失败")
-    }
-  }
-
-  async function handleImportWritingSkillFolder() {
-    if (!project || saving) return
-    try {
-      const selected = await open({ multiple: false, directory: true })
-      if (!selected || typeof selected !== "string") return
-      const config = await loadUserSkillConfig(project.path)
-      const next = await importLinkedSkill(config, selected)
-      if (!next.selectedSkillId) {
-        setMessage("导入失败:文件夹中未找到有效的 Skill 文件")
-        return
-      }
-      await persistWritingConfig(next, next.selectedSkillId)
-    } catch {
-      setMessage("导入失败:文件夹中未找到有效的 Skill 文件")
-    }
-  }
-
-  async function handleExportCurrentWritingSkill() {
-    if (!project || saving) return
-    try {
-      const config = await loadUserSkillConfig(project.path)
-      const selected = selectedWritingSkillId
-        ? config.skills.find((skill) => skill.id === selectedWritingSkillId)
-        : config.skills[0]
-      if (!selected) {
-        setMessage("请先选择写作 Skill")
-        return
-      }
-      const filePath = await save({
-        defaultPath: `${selected.name}.json`,
-        filters: [{ name: "JSON 文件", extensions: ["json"] }],
-      })
-      if (!filePath) return
-      await writeFile(filePath, exportSkillToJson(selected))
-      setMessage("导出成功")
-    } catch {
-      setMessage("导出 Skill 失败")
+      setMessage("导入失败")
     }
   }
 
@@ -357,12 +290,8 @@ function SkillLibraryHeaderActions({ activeTab }: { activeTab: "skillLibrary" |
           <button type="button" onClick={() => void handleCreateDeAiSkill()} disabled={disabled} className={buttonClass}>
             新建技能
           </button>
-          <span className="text-xs text-muted-foreground">导入技能</span>
           <button type="button" onClick={() => void handleImportDeAiSkillFile()} disabled={disabled} className={buttonClass}>
-            导入文件
-          </button>
-          <button type="button" onClick={() => void handleImportDeAiSkillFolder()} disabled={disabled} className={buttonClass}>
-            导入文件夹
+            导入
           </button>
         </>
       ) : (
@@ -370,15 +299,8 @@ function SkillLibraryHeaderActions({ activeTab }: { activeTab: "skillLibrary" |
           <button type="button" onClick={() => void handleCreateWritingSkill()} disabled={disabled} className={buttonClass}>
             新建 Skill
           </button>
-          <span className="text-xs text-muted-foreground">导入 Skill</span>
           <button type="button" onClick={() => void handleImportWritingSkill()} disabled={disabled} className={buttonClass}>
-            导入文件
-          </button>
-          <button type="button" onClick={() => void handleImportWritingSkillFolder()} disabled={disabled} className={buttonClass}>
-            导入文件夹
-          </button>
-          <button type="button" onClick={() => void handleExportCurrentWritingSkill()} disabled={disabled} className={buttonClass}>
-            导出当前
+            导入
           </button>
         </>
       )}
@@ -466,10 +388,12 @@ export function UnifiedSkillLibrarySidebarPanel() {
   function handleSelectEntry(entry: UnifiedSkillEntry) {
     if (entry.type === "writing") {
       setSelectedWritingSkillId(entry.sourceId)
+      setSelectedSkillId(null)
       setActiveView("writingSkillLibrary")
       return
     }
     setSelectedSkillId(entry.sourceId)
+    setSelectedWritingSkillId(null)
     setActiveView("skillLibrary")
   }
 
@@ -521,6 +445,7 @@ export function UnifiedSkillLibrarySidebarPanel() {
               key={entry.id}
               data-testid={`unified-skill-entry-${entry.id}`}
               role="button"
+              aria-current={active ? "true" : undefined}
               tabIndex={0}
               onClick={() => handleSelectEntry(entry)}
               onKeyDown={(event) => {

+ 14 - 0
src/components/skill-library/writing-skill-library-view.spec.tsx

@@ -96,6 +96,20 @@ describe("WritingSkillLibraryView", () => {
     ;(useWikiStore.getState() as any).setSelectedWritingSkillLibrarySkillId?.(null)
   })
 
+  it("shows one import action in the writing skill sidebar", async () => {
+    const { container, root } = await renderLibrary()
+    const sidebarButtons = Array.from(container.querySelectorAll<HTMLButtonElement>("button"))
+      .map((button) => button.textContent?.trim())
+
+    expect(sidebarButtons).toContain("导入")
+    expect(sidebarButtons).toContain("新建 Skill")
+    expect(sidebarButtons).not.toContain("导入文件")
+    expect(sidebarButtons).not.toContain("导入文件夹")
+    expect(container.textContent).not.toContain("导出当前")
+
+    cleanup(root, container)
+  })
+
   it("creates and saves a classified writing skill", async () => {
     const nowSpy = vi.spyOn(Date, "now").mockReturnValue(1234)
     const { container, root } = await renderLibrary()

+ 2 - 47
src/components/skill-library/writing-skill-library-view.tsx

@@ -7,7 +7,6 @@ import {
   deleteWritingSkill,
   deleteSkillCategory,
   exportSkillToJson,
-  importLinkedSkill,
   importSkillFromJson,
   importWritingSkill,
   loadAllLinkedSkillsContent,
@@ -407,43 +406,7 @@ export function WritingSkillLibrarySidebarPanel() {
       }
       await persist(next, next.selectedSkillId)
     } catch {
-      setMessage("导入 Skill 失败")
-    }
-  }
-
-  async function handleImportFolder() {
-    if (!config || !project || saving) return
-    if (draftDirty && !confirmDiscardSkillLibraryDraft()) return
-    if (draftDirty) setDraftDirty(false)
-    try {
-      const selected = await open({
-        multiple: false,
-        directory: true,
-      })
-      if (!selected || typeof selected !== "string") return
-      let next = await importLinkedSkill(config, selected)
-      const newSkillId = next.selectedSkillId
-      if (!newSkillId) {
-        setMessage("导入失败:文件夹中未找到有效的 Skill 文件")
-        return
-      }
-      if (selectedCategoryId !== "all" && selectedCategoryId !== "uncategorized") {
-        next = moveSkillToCategory(next, newSkillId, selectedCategoryId)
-      }
-      const newSkill = next.skills.find((s) => s.id === newSkillId)
-      if (newSkill && newSkill.source === "linked") {
-        try {
-          const content = await loadLinkedSkillContent(newSkill)
-          const updatedSkills = next.skills.map((s) =>
-            s.id === newSkillId ? { ...s, content } : s
-          )
-          next = { ...next, skills: updatedSkills }
-        } catch {
-        }
-      }
-      await persist(next, newSkillId)
-    } catch {
-      setMessage("导入失败:文件夹中未找到有效的 Skill 文件")
+      setMessage("导入失败")
     }
   }
 
@@ -637,15 +600,7 @@ export function WritingSkillLibrarySidebarPanel() {
             disabled={!config || !project || saving}
             className="rounded-md border px-2 py-1 text-xs hover:bg-accent disabled:opacity-50"
           >
-            导入文件
-          </button>
-          <button
-            type="button"
-            onClick={() => void handleImportFolder()}
-            disabled={!config || !project || saving}
-            className="rounded-md border px-2 py-1 text-xs hover:bg-accent disabled:opacity-50"
-          >
-            导入文件夹
+            导入
           </button>
           <button
             type="button"