fix(skills): move import button to header for better discoverability

- Add import button next to discover button in skills page header
- Expose openImport method via UnifiedSkillsPanel ref
- Show toast instead of dialog when no unmanaged skills found
- Remove redundant buttons from empty state view
This commit is contained in:
Jason
2026-01-03 09:21:15 +08:00
parent 6b73e55bfe
commit 6460c1d5dd
2 changed files with 32 additions and 37 deletions
+23 -9
View File
@@ -14,6 +14,7 @@ import {
Server,
RefreshCw,
Search,
Download,
} from "lucide-react";
import type { Provider } from "@/types";
import type { EnvConflict } from "@/types/env";
@@ -84,6 +85,7 @@ function App() {
const promptPanelRef = useRef<any>(null);
const mcpPanelRef = useRef<any>(null);
const skillsPageRef = useRef<any>(null);
const unifiedSkillsPanelRef = useRef<any>(null);
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";
@@ -423,6 +425,7 @@ function App() {
case "skills":
return (
<UnifiedSkillsPanel
ref={unifiedSkillsPanelRef}
onOpenDiscovery={() => setCurrentView("skillsDiscovery")}
/>
);
@@ -647,15 +650,26 @@ function App() {
</Button>
)}
{currentView === "skills" && (
<Button
variant="ghost"
size="sm"
onClick={() => setCurrentView("skillsDiscovery")}
className="hover:bg-black/5 dark:hover:bg-white/5"
>
<Search className="w-4 h-4 mr-2" />
{t("skills.discover")}
</Button>
<>
<Button
variant="ghost"
size="sm"
onClick={() => unifiedSkillsPanelRef.current?.openImport()}
className="hover:bg-black/5 dark:hover:bg-white/5"
>
<Download className="w-4 h-4 mr-2" />
{t("skills.import")}
</Button>
<Button
variant="ghost"
size="sm"
onClick={() => setCurrentView("skillsDiscovery")}
className="hover:bg-black/5 dark:hover:bg-white/5"
>
<Search className="w-4 h-4 mr-2" />
{t("skills.discover")}
</Button>
</>
)}
{currentView === "skillsDiscovery" && (
<>
+9 -28
View File
@@ -1,6 +1,6 @@
import React, { useMemo, useState } from "react";
import { useTranslation } from "react-i18next";
import { Sparkles, Trash2, ExternalLink, Download } from "lucide-react";
import { Sparkles, Trash2, ExternalLink } from "lucide-react";
import { Button } from "@/components/ui/button";
import { Switch } from "@/components/ui/switch";
import {
@@ -26,6 +26,7 @@ interface UnifiedSkillsPanelProps {
*/
export interface UnifiedSkillsPanelHandle {
openDiscovery: () => void;
openImport: () => void;
}
const UnifiedSkillsPanel = React.forwardRef<
@@ -98,7 +99,11 @@ const UnifiedSkillsPanel = React.forwardRef<
const handleOpenImport = async () => {
try {
await scanUnmanaged();
const result = await scanUnmanaged();
if (!result.data || result.data.length === 0) {
toast.success(t("skills.noUnmanagedFound"), { closeButton: true });
return;
}
setImportDialogOpen(true);
} catch (error) {
toast.error(t("common.error"), {
@@ -124,6 +129,7 @@ const UnifiedSkillsPanel = React.forwardRef<
React.useImperativeHandle(ref, () => ({
openDiscovery: onOpenDiscovery,
openImport: handleOpenImport,
}));
return (
@@ -152,18 +158,9 @@ const UnifiedSkillsPanel = React.forwardRef<
<h3 className="text-lg font-medium text-foreground mb-2">
{t("skills.noInstalled")}
</h3>
<p className="text-muted-foreground text-sm mb-4">
<p className="text-muted-foreground text-sm">
{t("skills.noInstalledDescription")}
</p>
<div className="flex gap-3 justify-center">
<Button onClick={onOpenDiscovery} variant="default">
{t("skills.discover")}
</Button>
<Button onClick={handleOpenImport} variant="outline">
<Download size={16} className="mr-2" />
{t("skills.import")}
</Button>
</div>
</div>
) : (
<div className="space-y-3">
@@ -369,22 +366,6 @@ const ImportSkillsDialog: React.FC<ImportSkillsDialogProps> = ({
onImport(Array.from(selected));
};
if (skills.length === 0) {
return (
<div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50">
<div className="bg-background rounded-xl p-6 max-w-md w-full mx-4 shadow-xl">
<h2 className="text-lg font-semibold mb-4">{t("skills.import")}</h2>
<p className="text-muted-foreground mb-6">
{t("skills.noUnmanagedFound")}
</p>
<div className="flex justify-end">
<Button onClick={onClose}>{t("common.close")}</Button>
</div>
</div>
</div>
);
}
return (
<div className="fixed inset-0 bg-black/50 flex items-center justify-center z-50">
<div className="bg-background rounded-xl p-6 max-w-lg w-full mx-4 shadow-xl max-h-[80vh] flex flex-col">