feat(skills): flag unmanaged skills with a green dot on the Import button

- Add an optional `enabled` flag to useScanUnmanagedSkills; the Skills panel
  scans once on mount (enabled:true), with 30s staleTime + keepPreviousData to
  dedupe disk IO across navigations.
- App subscribes to the shared query (enabled:false) and renders a green dot +
  tooltip on the top-bar Import button when unmanaged skills are available.
This commit is contained in:
Jason
2026-06-24 23:47:04 +08:00
parent a4eb5f3778
commit f1328d89fc
3 changed files with 28 additions and 4 deletions
+17 -1
View File
@@ -46,6 +46,7 @@ import { useAutoCompact } from "@/hooks/useAutoCompact";
import { useUsageCacheBridge } from "@/hooks/useUsageCacheBridge";
import { useTauriEvent } from "@/hooks/useTauriEvent";
import { useLastValidValue } from "@/hooks/useLastValidValue";
import { useScanUnmanagedSkills } from "@/hooks/useSkills";
import { extractErrorMessage } from "@/utils/errorUtils";
import { isTextEditableTarget } from "@/utils/domUtils";
import { deepClone } from "@/utils/deepClone";
@@ -250,6 +251,10 @@ function App() {
const mcpPanelRef = useRef<any>(null);
const skillsPageRef = useRef<any>(null);
const unifiedSkillsPanelRef = useRef<any>(null);
// 订阅未管理 Skill 的共享缓存(实际扫描由 UnifiedSkillsPanel 进入页面时触发)。
// 这里 enabled 默认 false,仅用于「导入」按钮的绿点提示,不主动发起扫描。
const { data: unmanagedSkills } = useScanUnmanagedSkills();
const hasUnmanagedSkills = (unmanagedSkills?.length ?? 0) > 0;
const addActionButtonClass =
"bg-orange-500 hover:bg-orange-600 dark:bg-orange-500 dark:hover:bg-orange-600 text-white shadow-lg shadow-orange-500/30 dark:shadow-orange-500/40 rounded-full w-8 h-8";
@@ -1315,10 +1320,21 @@ function App() {
onClick={() =>
unifiedSkillsPanelRef.current?.openImport()
}
className="hover:bg-black/5 dark:hover:bg-white/5"
className="relative hover:bg-black/5 dark:hover:bg-white/5"
title={
hasUnmanagedSkills
? t("skills.unmanagedAvailable")
: undefined
}
>
<Download className="w-4 h-4 mr-2" />
{t("skills.import")}
{hasUnmanagedSkills && (
<span
className="absolute top-1 right-1 h-2 w-2 rounded-full bg-green-500"
aria-hidden="true"
/>
)}
</Button>
<Button
variant="ghost"
+2 -1
View File
@@ -90,8 +90,9 @@ const UnifiedSkillsPanel = React.forwardRef<
const toggleAppMutation = useToggleSkillApp();
const uninstallMutation = useUninstallSkill();
const restoreBackupMutation = useRestoreSkillBackup();
// enabled: true —— 进入 Skill 页面时自动静默扫描一次(绿点提示来源)
const { data: unmanagedSkills, refetch: scanUnmanaged } =
useScanUnmanagedSkills();
useScanUnmanagedSkills({ enabled: true });
const importMutation = useImportSkillsFromApps();
const installFromZipMutation = useInstallSkillsFromZip();
const {
+9 -2
View File
@@ -187,12 +187,19 @@ export function useToggleSkillApp() {
/**
* 扫描未管理的 Skills
*
* - 传 { enabled: true }Skill 面板挂载时)会在进入页面时自动静默扫描一次,
* 30s 内复用结果,避免来回切页时重复磁盘 IO。
* - 默认 enabled: false:仅订阅共享缓存(如顶栏「导入」按钮的绿点提示),
* 不主动触发扫描。两者共用同一 queryKey,面板扫描完成后绿点会自动亮起。
*/
export function useScanUnmanagedSkills() {
export function useScanUnmanagedSkills(options?: { enabled?: boolean }) {
return useQuery({
queryKey: ["skills", "unmanaged"],
queryFn: () => skillsApi.scanUnmanaged(),
enabled: false, // 手动触发
enabled: options?.enabled ?? false,
staleTime: 30 * 1000,
placeholderData: keepPreviousData,
});
}