From cdf1a04e26f33a8e05bbf9b28afc98747c220b0a Mon Sep 17 00:00:00 2001 From: Simos Mikelatos Date: Tue, 30 Jun 2026 10:29:19 +0000 Subject: [PATCH] fix(redesign): redesign hermes skills add flow --- src/components/skills/view/ProviderSkills.tsx | 580 ++++++++++-------- 1 file changed, 321 insertions(+), 259 deletions(-) diff --git a/src/components/skills/view/ProviderSkills.tsx b/src/components/skills/view/ProviderSkills.tsx index 05ad4628..21fc8cb8 100644 --- a/src/components/skills/view/ProviderSkills.tsx +++ b/src/components/skills/view/ProviderSkills.tsx @@ -8,9 +8,11 @@ import { FileUp, FolderUp, Loader2, + Plus, RefreshCw, Search, Upload, + Wrench, X, } from 'lucide-react'; import { useTranslation } from 'react-i18next'; @@ -24,6 +26,9 @@ import { CardDescription, CardHeader, CardTitle, + Dialog, + DialogContent, + DialogTitle, Input, } from '../../../shared/view/ui'; import { useProviderSkills } from '../hooks/useProviderSkills'; @@ -248,6 +253,8 @@ export default function ProviderSkills({ selectedProvider, currentProjects }: Pr const [isSubmitting, setIsSubmitting] = useState(false); const [searchQuery, setSearchQuery] = useState(''); const [registryQuery, setRegistryQuery] = useState(''); + const [isAddDialogOpen, setIsAddDialogOpen] = useState(false); + const [addMode, setAddMode] = useState<'upload' | 'hub'>('upload'); const fileInputRef = useRef(null); const folderInputRef = useRef(null); @@ -259,6 +266,9 @@ export default function ProviderSkills({ selectedProvider, currentProjects }: Pr setSubmitError(null); setIsSubmitting(false); setSearchQuery(''); + setRegistryQuery(''); + setIsAddDialogOpen(false); + setAddMode('upload'); }, [selectedProvider]); useEffect(() => { @@ -386,6 +396,7 @@ export default function ProviderSkills({ selectedProvider, currentProjects }: Pr }))); await addSkills({ entries }); setQueuedFiles([]); + setIsAddDialogOpen(false); } catch (error) { setSubmitError(error instanceof Error ? error.message : 'Failed to import skills'); } finally { @@ -393,6 +404,231 @@ export default function ProviderSkills({ selectedProvider, currentProjects }: Pr } }, [addSkills, queuedFiles]); + const uploadPanel = ( +
+
+
Install Path
+ {providerPath} +
+ +
+ { + handleDrop(Array.from(event.target.files ?? [])); + event.target.value = ''; + }} + /> + { + handleFolderSelection(Array.from(event.target.files ?? [])); + event.target.value = ''; + }} + /> +
+ +
+
Drop `.md` files or skill folders here
+
+ Upload standalone definitions or choose a full folder to include scripts, references, and assets. +
+
+
+ + +
+
+
+ + {queuedFiles.length > 0 && ( +
+
Queued Files
+
+ {queuedFiles.map((queuedFile) => ( +
+
+
{queuedFile.name}
+
+ {queuedFile.kind === 'folder' + ? `${queuedFile.files.length} files` + : 'Markdown file'} + {' · '} + {formatFileSize(queuedFile.size)} +
+
+ +
+ ))} +
+
+ )} + +
+ + + Folder uploads keep the selected folder name; standalone files use the `name` in `SKILL.md`. + +
+
+ ); + + const hermesHubPanel = selectedProvider === 'hermes' ? ( +
+
+
+ + setRegistryQuery(event.target.value)} + onKeyDown={(event) => { + if (event.key === 'Enter') { + void searchRegistry(registryQuery); + } + }} + placeholder="Search Hermes skills..." + aria-label="Search Hermes skills registry" + className="h-9 w-full pl-9" + /> +
+ +
+ +
+
+ + Hub Maintenance +
+
+ {HERMES_SKILL_ACTIONS.map((action) => { + const Icon = action.icon; + return ( + + ); + })} +
+
+ + {registryResults.length > 0 && ( +
+ {registryResults.map((result) => ( +
+
+
+ {result.name} + {result.source && ( + {result.source} + )} + {result.trustLevel && ( + {result.trustLevel} + )} +
+
{result.identifier}
+ {result.description && ( +
{result.description}
+ )} +
+ +
+ ))} +
+ )} +
+ ) : null; + return (
@@ -408,279 +644,105 @@ export default function ProviderSkills({ selectedProvider, currentProjects }: Pr
- +
+ + +
- {selectedProvider === 'hermes' && ( -
-
-
-
- - Hermes Skills Hub + + + Add Skill +
+
+
+ {addMode === 'hub' ? : }
-
- Search the Hermes registry, install skills, and keep installed hub skills current. +
+
Add {providerName} Skill
+
+ {selectedProvider === 'hermes' + ? 'Upload a local skill or install one from the Hermes Skills Hub.' + : 'Upload a markdown skill file or a complete skill folder.'} +
-
-
-
- - setRegistryQuery(event.target.value)} - onKeyDown={(event) => { - if (event.key === 'Enter') { - void searchRegistry(registryQuery); - } - }} - placeholder="Search Hermes skills..." - aria-label="Search Hermes skills registry" - className="h-9 w-full pl-9" - /> -
- -
-
- {HERMES_SKILL_ACTIONS.map((action) => { - const Icon = action.icon; - return ( - - ); - })} -
- - {(registryError || registryStatus) && ( -
- {registryError || registryStatus} -
- )} - - {registryResults.length > 0 && ( -
- {registryResults.map((result) => ( -
+ Upload + + -
- ))} -
- )} -
- )} - - - -
-
Upload Skills
-
-
Install Path
- {providerPath} -
-
-
- - -
-
- { - handleDrop(Array.from(event.target.files ?? [])); - event.target.value = ''; - }} - /> - { - handleFolderSelection(Array.from(event.target.files ?? [])); - event.target.value = ''; - }} - /> -
- -
-
Drop `.md` files or skill folders here
-
- Upload standalone definitions or choose a full folder to include its scripts, references, and assets. -
-
-
- - -
-
-
- - {queuedFiles.length > 0 && ( -
-
Queued Files
-
- {queuedFiles.map((queuedFile) => ( -
-
-
{queuedFile.name}
-
- {queuedFile.kind === 'folder' - ? `${queuedFile.files.length} files` - : 'Markdown file'} - {' · '} - {formatFileSize(queuedFile.size)} -
-
- -
- ))} -
+ + Skills Hub +
)} - -
- - - Folder uploads keep the selected folder name; standalone files use the `name` in `SKILL.md`. - -
- {(submitError || loadError) && ( -
- {submitError || loadError} -
- )} +
+ {addMode === 'hub' && hermesHubPanel ? hermesHubPanel : uploadPanel} - {saveStatus === 'success' && ( -
- - Skills saved successfully. -
- )} - - + {(submitError || loadError || registryError || registryStatus || saveStatus === 'success') && ( +
+ {submitError || loadError || registryError || registryStatus || 'Skills saved successfully.'} +
+ )} +
+
+
+ + {saveStatus === 'success' && !isAddDialogOpen && ( +
+ + Skills saved successfully. +
+ )}