Skip to content

Commit 3c67270

Browse files
Update skills UI components for managed skills
1 parent 6c7e882 commit 3c67270

5 files changed

Lines changed: 55 additions & 74 deletions

File tree

frontend/src/components/repo/RepoSkillsDialog.tsx

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,13 +21,15 @@ export function RepoSkillsDialog({ open, onOpenChange, repoId }: RepoSkillsDialo
2121
return null
2222
}
2323

24+
const repoSkills = data?.filter(skill => skill.scope === 'project')
25+
2426
return (
2527
<Dialog open={open} onOpenChange={onOpenChange}>
26-
<DialogContent className="sm:max-w-[600px] w-full">
27-
<DialogHeader>
28+
<DialogContent mobileFullscreen className="sm:max-w-2xl sm:max-h-[85vh] gap-0 flex flex-col p-0 md:p-6 pb-safe">
29+
<DialogHeader className="p-4 sm:p-6 border-b flex flex-row items-center justify-between space-y-0 shrink-0">
2830
<DialogTitle>Skills</DialogTitle>
2931
</DialogHeader>
30-
<RepoSkillsList isLoading={isLoading} data={data} error={error} />
32+
<RepoSkillsList isLoading={isLoading} data={repoSkills} error={error} />
3133
</DialogContent>
3234
</Dialog>
3335
)

frontend/src/components/repo/RepoSkillsList.tsx

Lines changed: 14 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
import { Badge } from '@/components/ui/badge'
2-
import { AlertCircle, Loader2, Sparkles } from 'lucide-react'
1+
import { AlertCircle, Loader2 } from 'lucide-react'
32
import type { SkillFileInfo } from '@opencode-manager/shared'
43

54
interface RepoSkillsListProps {
@@ -36,46 +35,28 @@ export function RepoSkillsList({ isLoading, data, error }: RepoSkillsListProps)
3635
if (!data || data.length === 0) {
3736
return (
3837
<div className="text-center py-6 text-muted-foreground">
39-
<Sparkles className="w-10 h-10 mx-auto mb-3 opacity-50" />
4038
<p className="text-sm">No skills available</p>
41-
<p className="text-xs mt-1">Skills will appear here when configured in Settings or in the project's .opencode/skills/ directory</p>
39+
<p className="text-xs mt-1">Skills will appear here when configured in the project's .opencode/skills/ directory</p>
4240
</div>
4341
)
4442
}
4543

4644
return (
47-
<div className="px-4 sm:px-6 py-3 sm:py-4 flex-1 overflow-y-auto min-h-0">
48-
<div className="space-y-3">
45+
<div className="flex-1 overflow-y-auto min-h-0 px-2 sm:px-4 py-2">
46+
<div className="space-y-2">
4947
{data.map((skill) => (
5048
<div
51-
key={`${skill.scope}-${skill.name}`}
52-
className="flex items-start justify-between gap-3 p-3 rounded-lg border border-border bg-card"
49+
key={skill.name}
50+
className="p-2 rounded-lg border border-border bg-card"
5351
>
54-
<div className="flex-1 min-w-0">
55-
<div className="flex items-center gap-2 mb-1">
56-
<p className="text-sm font-medium truncate">
57-
{formatSkillName(skill.name)}
58-
</p>
59-
<Badge variant={skill.scope === 'global' ? 'secondary' : 'outline'} className="text-xs">
60-
{skill.scope === 'global' ? 'Global' : 'Project'}
61-
</Badge>
62-
</div>
63-
{skill.description && (
64-
<p className="text-xs text-muted-foreground line-clamp-2">
65-
{skill.description}
66-
</p>
67-
)}
68-
{skill.scope === 'project' && skill.repoName && (
69-
<p className="text-xs text-muted-foreground mt-1">
70-
Repo: {skill.repoName}
71-
</p>
72-
)}
73-
{skill.body && (
74-
<div className="text-xs font-mono bg-muted rounded p-2 mt-2 line-clamp-2 max-h-[40px] overflow-hidden whitespace-pre-wrap">
75-
{skill.body}
76-
</div>
77-
)}
78-
</div>
52+
<p className="text-sm font-medium truncate">
53+
{formatSkillName(skill.name)}
54+
</p>
55+
{skill.description && (
56+
<p className="text-xs text-muted-foreground truncate mt-0.5">
57+
{skill.description}
58+
</p>
59+
)}
7960
</div>
8061
))}
8162
</div>

frontend/src/components/settings/OpenCodeConfigManager.tsx

Lines changed: 29 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import { SkillsEditor } from './SkillsEditor'
1717
import { VersionSelectDialog } from './VersionSelectDialog'
1818
import { MemoryPluginConfig } from './MemoryPluginConfig'
1919
import { settingsApi } from '@/api/settings'
20-
import { useMutation, useQueryClient } from '@tanstack/react-query'
20+
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
2121
import { useServerHealth } from '@/hooks/useServerHealth'
2222
import { parseJsonc, hasJsoncComments } from '@/lib/jsonc'
2323
import { showToast } from '@/lib/toast'
@@ -77,6 +77,12 @@ export function OpenCodeConfigManager() {
7777
const skillsRef = useRef<HTMLButtonElement>(null)
7878
const mcpRef = useRef<HTMLButtonElement>(null)
7979

80+
const { data: managedSkills = [] } = useQuery({
81+
queryKey: ['managed-skills'],
82+
queryFn: () => settingsApi.listManagedSkills(),
83+
staleTime: 5 * 60 * 1000,
84+
})
85+
8086
const scrollToSection = (ref: React.RefObject<HTMLButtonElement | null>) => {
8187
if (ref.current) {
8288
ref.current.scrollIntoView({
@@ -563,7 +569,7 @@ export function OpenCodeConfigManager() {
563569
<div className="bg-card border border-border rounded-lg overflow-hidden min-w-0 mb-6">
564570
<button
565571
ref={agentsMdRef}
566-
className="w-full px-4 py-3 flex items-center justify-between hover:bg-muted/50 transition-colors min-w-0"
572+
className={cn("w-full px-4 py-3 flex items-center justify-between transition-colors min-w-0", expandedSections.agentsMd ? "bg-muted/40 hover:bg-muted/50" : "hover:bg-muted/50")}
567573
onClick={() => {
568574
const isExpanding = !expandedSections.agentsMd
569575
setExpandedSections(prev => ({ ...prev, agentsMd: isExpanding }))
@@ -620,7 +626,7 @@ export function OpenCodeConfigManager() {
620626
<div className="bg-card border border-border rounded-lg overflow-hidden min-w-0">
621627
<button
622628
ref={commandsRef}
623-
className="w-full px-4 py-3 flex items-center justify-between hover:bg-muted/50 transition-colors min-w-0"
629+
className={cn("w-full px-4 py-3 flex items-center justify-between transition-colors min-w-0", expandedSections.commands ? "bg-muted/40 hover:bg-muted/50" : "hover:bg-muted/50")}
624630
onClick={() => {
625631
const isExpanding = !expandedSections.commands
626632
setExpandedSections(prev => ({ ...prev, commands: isExpanding }))
@@ -657,7 +663,7 @@ export function OpenCodeConfigManager() {
657663
<div className="bg-card border border-border rounded-lg overflow-hidden min-w-0">
658664
<button
659665
ref={agentsRef}
660-
className="w-full px-4 py-3 flex items-center justify-between hover:bg-muted/50 transition-colors min-w-0"
666+
className={cn("w-full px-4 py-3 flex items-center justify-between transition-colors min-w-0", expandedSections.agents ? "bg-muted/40 hover:bg-muted/50" : "hover:bg-muted/50")}
661667
onClick={() => {
662668
const isExpanding = !expandedSections.agents
663669
setExpandedSections(prev => ({ ...prev, agents: isExpanding }))
@@ -694,7 +700,7 @@ export function OpenCodeConfigManager() {
694700
<div className="bg-card border border-border rounded-lg overflow-hidden min-w-0">
695701
<button
696702
ref={skillsRef}
697-
className="w-full px-4 py-3 flex items-center justify-between hover:bg-muted/50 transition-colors min-w-0"
703+
className={cn("w-full px-4 py-3 flex items-center justify-between transition-colors min-w-0", expandedSections.skills ? "bg-muted/40 hover:bg-muted/50" : "hover:bg-muted/50")}
698704
onClick={() => {
699705
const isExpanding = !expandedSections.skills
700706
setExpandedSections(prev => ({ ...prev, skills: isExpanding }))
@@ -706,33 +712,34 @@ export function OpenCodeConfigManager() {
706712
<div className="flex items-center gap-3 min-w-0">
707713
<h4 className="text-sm font-medium truncate">Skills</h4>
708714
<span className="text-xs text-muted-foreground">
709-
{(selectedConfig.content?.skills?.paths?.length ?? 0) + (selectedConfig.content?.skills?.urls?.length ?? 0)} configured
715+
{managedSkills.length + (selectedConfig.content?.skills?.paths?.length ?? 0) + (selectedConfig.content?.skills?.urls?.length ?? 0)} configured
710716
</span>
711717
</div>
712718
<ChevronDown className={`h-4 w-4 transition-transform ${expandedSections.skills ? 'rotate-90' : ''}`} />
713719
</button>
714-
<div className={`${expandedSections.skills ? 'block' : 'hidden'} border-t border-border`}>
715-
<div className="p-4 max-h-[50vh] overflow-y-auto">
716-
<SkillsEditor
717-
skills={selectedConfig.content?.skills}
718-
onChange={(skills) => {
719-
const paths = skills?.paths?.filter(Boolean)
720-
const urls = skills?.urls?.filter(Boolean)
721-
const updatedContent = {
722-
...selectedConfig.content,
723-
skills: (paths?.length || urls?.length) ? { paths: paths?.length ? paths : undefined, urls: urls?.length ? urls : undefined } : undefined
724-
}
725-
updateConfigContent(selectedConfig.name, updatedContent)
726-
}}
727-
/>
720+
<div className={`${expandedSections.skills ? 'block' : 'hidden'} border-t border-border`}>
721+
<div className="p-4 max-h-[50vh] overflow-y-auto">
722+
<SkillsEditor
723+
skills={selectedConfig.content?.skills}
724+
managedSkills={managedSkills}
725+
onChange={(skills) => {
726+
const paths = skills?.paths?.filter(Boolean)
727+
const urls = skills?.urls?.filter(Boolean)
728+
const updatedContent = {
729+
...selectedConfig.content,
730+
skills: (paths?.length || urls?.length) ? { paths: paths?.length ? paths : undefined, urls: urls?.length ? urls : undefined } : undefined
731+
}
732+
updateConfigContent(selectedConfig.name, updatedContent)
733+
}}
734+
/>
735+
</div>
728736
</div>
729-
</div>
730737
</div>
731738

732739
<div className="bg-card border border-border rounded-lg overflow-hidden min-w-0">
733740
<button
734741
ref={mcpRef}
735-
className="w-full px-4 py-3 flex items-center justify-between hover:bg-muted/50 transition-colors min-w-0"
742+
className={cn("w-full px-4 py-3 flex items-center justify-between transition-colors min-w-0", expandedSections.mcp ? "bg-muted/40 hover:bg-muted/50" : "hover:bg-muted/50")}
736743
onClick={() => {
737744
const isExpanding = !expandedSections.mcp
738745
setExpandedSections(prev => ({ ...prev, mcp: isExpanding }))

frontend/src/components/settings/SkillDialog.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -115,7 +115,7 @@ export function SkillDialog({ open, onOpenChange, onSubmit, editingSkill }: Skil
115115

116116
return (
117117
<Dialog open={open} onOpenChange={handleOpenChange}>
118-
<DialogContent mobileFullscreen className="sm:max-w-2xl sm:max-h-[85vh] gap-0 flex flex-col p-0 md:p-6">
118+
<DialogContent mobileFullscreen className="sm:max-w-2xl sm:max-h-[85vh] gap-0 flex flex-col p-0 md:p-6 pb-safe">
119119
<DialogHeader className="p-4 sm:p-6 border-b flex flex-row items-center justify-between space-y-0">
120120
<DialogTitle>{editingSkill ? 'Edit Skill' : 'Create Skill'}</DialogTitle>
121121
</DialogHeader>
@@ -270,7 +270,7 @@ export function SkillDialog({ open, onOpenChange, onSubmit, editingSkill }: Skil
270270
</Form>
271271
</div>
272272

273-
<DialogFooter className="p-3 sm:p-4 border-t gap-2 pb-4">
273+
<DialogFooter className="flex flex-row gap-2 pt-2 border-t border-border sm:justify-end pb-4 p-3">
274274
<Button variant="outline" onClick={() => handleOpenChange(false)} className="flex-1 sm:flex-none">
275275
Cancel
276276
</Button>

frontend/src/components/settings/SkillsEditor.tsx

Lines changed: 5 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { useState, useEffect } from 'react'
2-
import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
3-
import { Plus, Trash2, Loader2, Pencil, ChevronDown, ChevronRight, Save, X } from 'lucide-react'
2+
import { useMutation, useQueryClient } from '@tanstack/react-query'
3+
import { Plus, Trash2, Pencil, ChevronDown, ChevronRight, Save, X } from 'lucide-react'
44
import { Button } from '@/components/ui/button'
55
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
66
import { Input } from '@/components/ui/input'
@@ -14,6 +14,7 @@ import { toast } from 'sonner'
1414

1515
interface SkillsEditorProps {
1616
skills?: OpenCodeConfigInput['skills']
17+
managedSkills?: SkillFileInfo[]
1718
onChange?: (skills: OpenCodeConfigInput['skills']) => void
1819
}
1920

@@ -77,7 +78,7 @@ function SkillPathEditor({ items, onChange, onAddItem, onRemoveItem, label, plac
7778
)
7879
}
7980

80-
export function SkillsEditor({ skills, onChange }: SkillsEditorProps) {
81+
export function SkillsEditor({ skills, managedSkills = [], onChange }: SkillsEditorProps) {
8182
const [dialogOpen, setDialogOpen] = useState(false)
8283
const [editingSkill, setEditingSkill] = useState<SkillFileInfo | null>(null)
8384
const [collapsiblesOpen, setCollapsiblesOpen] = useState({
@@ -98,12 +99,6 @@ export function SkillsEditor({ skills, onChange }: SkillsEditorProps) {
9899

99100
const queryClient = useQueryClient()
100101

101-
const { data: managedSkills = [], isLoading: isLoadingManaged } = useQuery({
102-
queryKey: ['managed-skills'],
103-
queryFn: () => settingsApi.listManagedSkills(),
104-
staleTime: 5 * 60 * 1000,
105-
})
106-
107102
const createMutation = useMutation({
108103
mutationFn: (data: CreateSkillRequest) => settingsApi.createSkill(data),
109104
onSuccess: () => {
@@ -238,11 +233,7 @@ export function SkillsEditor({ skills, onChange }: SkillsEditorProps) {
238233

239234
{collapsiblesOpen.managed && (
240235
<>
241-
{isLoadingManaged ? (
242-
<div className="flex items-center justify-center py-8">
243-
<Loader2 className="h-6 w-6 animate-spin text-muted-foreground" />
244-
</div>
245-
) : managedSkills.length === 0 ? (
236+
{managedSkills.length === 0 ? (
246237
<Card>
247238
<CardContent className="p-6">
248239
<div className="flex items-center gap-2 mb-2">

0 commit comments

Comments
 (0)