@octo-cyber/ai 0.5.1 → 0.5.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/ai.module.d.ts +1 -0
- package/dist/ai.module.d.ts.map +1 -1
- package/dist/ai.module.js +21 -2
- package/dist/ai.module.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -1
- package/dist/index.js.map +1 -1
- package/dist/skill/controllers/skill.controller.d.ts +33 -0
- package/dist/skill/controllers/skill.controller.d.ts.map +1 -0
- package/dist/skill/controllers/skill.controller.js +264 -0
- package/dist/skill/controllers/skill.controller.js.map +1 -0
- package/dist/skill/entities/octo-skill.entity.d.ts +22 -0
- package/dist/skill/entities/octo-skill.entity.d.ts.map +1 -0
- package/dist/skill/entities/octo-skill.entity.js +91 -0
- package/dist/skill/entities/octo-skill.entity.js.map +1 -0
- package/dist/skill/index.d.ts +7 -0
- package/dist/skill/index.d.ts.map +1 -0
- package/dist/skill/index.js +17 -0
- package/dist/skill/index.js.map +1 -0
- package/dist/skill/services/skill-generator.service.d.ts +47 -0
- package/dist/skill/services/skill-generator.service.d.ts.map +1 -0
- package/dist/skill/services/skill-generator.service.js +232 -0
- package/dist/skill/services/skill-generator.service.js.map +1 -0
- package/dist/skill/services/skill-installer.service.d.ts +38 -0
- package/dist/skill/services/skill-installer.service.d.ts.map +1 -0
- package/dist/skill/services/skill-installer.service.js +150 -0
- package/dist/skill/services/skill-installer.service.js.map +1 -0
- package/dist/skill/services/skill-registry.service.d.ts +60 -0
- package/dist/skill/services/skill-registry.service.d.ts.map +1 -0
- package/dist/skill/services/skill-registry.service.js +147 -0
- package/dist/skill/services/skill-registry.service.js.map +1 -0
- package/dist/skill/types.d.ts +45 -0
- package/dist/skill/types.d.ts.map +1 -0
- package/dist/skill/types.js +6 -0
- package/dist/skill/types.js.map +1 -0
- package/package.json +5 -5
- package/web/components/SkillCard.tsx +139 -0
- package/web/components/SkillEditorDialog.tsx +77 -0
- package/web/components/SkillInstallDialog.tsx +134 -0
- package/web/index.ts +2 -0
- package/web/manifest.ts +1 -0
- package/web/messages/en-US.json +53 -0
- package/web/messages/zh-CN.json +53 -0
- package/web/pages/SkillsPage.tsx +206 -0
- package/web/services/skill-service.ts +89 -0
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { useState, useEffect, useCallback } from 'react'
|
|
4
|
+
import { useTranslations } from 'next-intl'
|
|
5
|
+
import { toast } from 'sonner'
|
|
6
|
+
import {
|
|
7
|
+
RefreshCw,
|
|
8
|
+
Plus,
|
|
9
|
+
Loader2,
|
|
10
|
+
Sparkles,
|
|
11
|
+
} from 'lucide-react'
|
|
12
|
+
|
|
13
|
+
import { PageHeader } from '@octo-cyber/ui/components/shared/page-header'
|
|
14
|
+
import { Button } from '@octo-cyber/ui/components/ui/button'
|
|
15
|
+
import { Tabs, TabsList, TabsTrigger } from '@octo-cyber/ui/components/ui/tabs'
|
|
16
|
+
|
|
17
|
+
import { skillService } from '../services/skill-service'
|
|
18
|
+
import type { SkillInfo, SkillTargetInfo } from '../services/skill-service'
|
|
19
|
+
import SkillCard from '../components/SkillCard'
|
|
20
|
+
import SkillInstallDialog from '../components/SkillInstallDialog'
|
|
21
|
+
import SkillEditorDialog from '../components/SkillEditorDialog'
|
|
22
|
+
|
|
23
|
+
type TabFilter = 'all' | 'enabled' | 'installed'
|
|
24
|
+
|
|
25
|
+
export default function SkillsPage() {
|
|
26
|
+
const t = useTranslations('aiSkills')
|
|
27
|
+
const tc = useTranslations('common')
|
|
28
|
+
|
|
29
|
+
const [skills, setSkills] = useState<SkillInfo[]>([])
|
|
30
|
+
const [targets, setTargets] = useState<SkillTargetInfo[]>([])
|
|
31
|
+
const [loading, setLoading] = useState(true)
|
|
32
|
+
const [generating, setGenerating] = useState(false)
|
|
33
|
+
const [tab, setTab] = useState<TabFilter>('all')
|
|
34
|
+
|
|
35
|
+
// Dialog states
|
|
36
|
+
const [installSkill, setInstallSkill] = useState<SkillInfo | null>(null)
|
|
37
|
+
const [editSkill, setEditSkill] = useState<SkillInfo | null>(null)
|
|
38
|
+
|
|
39
|
+
const loadData = useCallback(async () => {
|
|
40
|
+
setLoading(true)
|
|
41
|
+
try {
|
|
42
|
+
const [skillList, targetList] = await Promise.all([
|
|
43
|
+
skillService.list(),
|
|
44
|
+
skillService.getTargets(),
|
|
45
|
+
])
|
|
46
|
+
setSkills(skillList)
|
|
47
|
+
setTargets(targetList)
|
|
48
|
+
} catch {
|
|
49
|
+
toast.error(t('toast.loadFailed'))
|
|
50
|
+
} finally {
|
|
51
|
+
setLoading(false)
|
|
52
|
+
}
|
|
53
|
+
}, [t])
|
|
54
|
+
|
|
55
|
+
useEffect(() => { loadData() }, [loadData])
|
|
56
|
+
|
|
57
|
+
const handleGenerate = useCallback(async () => {
|
|
58
|
+
setGenerating(true)
|
|
59
|
+
try {
|
|
60
|
+
const result = await skillService.generate()
|
|
61
|
+
toast.success(t('toast.generateDone', {
|
|
62
|
+
created: result.created.length,
|
|
63
|
+
updated: result.updated.length,
|
|
64
|
+
}))
|
|
65
|
+
await loadData()
|
|
66
|
+
} catch {
|
|
67
|
+
toast.error(t('toast.generateFailed'))
|
|
68
|
+
} finally {
|
|
69
|
+
setGenerating(false)
|
|
70
|
+
}
|
|
71
|
+
}, [t, loadData])
|
|
72
|
+
|
|
73
|
+
const handleToggle = useCallback(async (id: string) => {
|
|
74
|
+
try {
|
|
75
|
+
await skillService.toggle(id)
|
|
76
|
+
await loadData()
|
|
77
|
+
} catch {
|
|
78
|
+
toast.error(t('toast.toggleFailed'))
|
|
79
|
+
}
|
|
80
|
+
}, [t, loadData])
|
|
81
|
+
|
|
82
|
+
const handleDelete = useCallback(async (id: string) => {
|
|
83
|
+
try {
|
|
84
|
+
await skillService.deleteSkill(id)
|
|
85
|
+
toast.success(t('toast.deleted'))
|
|
86
|
+
await loadData()
|
|
87
|
+
} catch {
|
|
88
|
+
toast.error(t('toast.deleteFailed'))
|
|
89
|
+
}
|
|
90
|
+
}, [t, loadData])
|
|
91
|
+
|
|
92
|
+
const handleInstallDone = useCallback(() => {
|
|
93
|
+
setInstallSkill(null)
|
|
94
|
+
loadData()
|
|
95
|
+
}, [loadData])
|
|
96
|
+
|
|
97
|
+
const handleEditSave = useCallback(async (id: string, content: string) => {
|
|
98
|
+
try {
|
|
99
|
+
await skillService.update(id, { content })
|
|
100
|
+
toast.success(t('toast.saved'))
|
|
101
|
+
setEditSkill(null)
|
|
102
|
+
await loadData()
|
|
103
|
+
} catch {
|
|
104
|
+
toast.error(t('toast.saveFailed'))
|
|
105
|
+
}
|
|
106
|
+
}, [t, loadData])
|
|
107
|
+
|
|
108
|
+
// Filter skills by tab
|
|
109
|
+
const filtered = skills.filter((s) => {
|
|
110
|
+
if (tab === 'enabled') return s.isEnabled
|
|
111
|
+
if (tab === 'installed') return s.installedTargets.length > 0
|
|
112
|
+
return true
|
|
113
|
+
})
|
|
114
|
+
|
|
115
|
+
return (
|
|
116
|
+
<div className="space-y-6">
|
|
117
|
+
<PageHeader title={t('title')} description={t('description')}>
|
|
118
|
+
<Button
|
|
119
|
+
variant="outline"
|
|
120
|
+
size="sm"
|
|
121
|
+
onClick={handleGenerate}
|
|
122
|
+
disabled={generating}
|
|
123
|
+
>
|
|
124
|
+
{generating ? (
|
|
125
|
+
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
|
|
126
|
+
) : (
|
|
127
|
+
<Sparkles className="mr-2 h-4 w-4" />
|
|
128
|
+
)}
|
|
129
|
+
{t('actions.generate')}
|
|
130
|
+
</Button>
|
|
131
|
+
<Button
|
|
132
|
+
variant="outline"
|
|
133
|
+
size="sm"
|
|
134
|
+
onClick={loadData}
|
|
135
|
+
disabled={loading}
|
|
136
|
+
>
|
|
137
|
+
<RefreshCw className="mr-2 h-4 w-4" />
|
|
138
|
+
{tc('refresh')}
|
|
139
|
+
</Button>
|
|
140
|
+
</PageHeader>
|
|
141
|
+
|
|
142
|
+
<Tabs value={tab} onValueChange={(v) => setTab(v as TabFilter)}>
|
|
143
|
+
<TabsList>
|
|
144
|
+
<TabsTrigger value="all">{t('tabs.all')}</TabsTrigger>
|
|
145
|
+
<TabsTrigger value="enabled">{t('tabs.enabled')}</TabsTrigger>
|
|
146
|
+
<TabsTrigger value="installed">{t('tabs.installed')}</TabsTrigger>
|
|
147
|
+
</TabsList>
|
|
148
|
+
</Tabs>
|
|
149
|
+
|
|
150
|
+
{loading ? (
|
|
151
|
+
<div className="flex items-center justify-center py-12">
|
|
152
|
+
<Loader2 className="h-6 w-6 animate-spin text-muted-foreground" />
|
|
153
|
+
</div>
|
|
154
|
+
) : filtered.length === 0 ? (
|
|
155
|
+
<div className="flex flex-col items-center justify-center py-12 text-muted-foreground">
|
|
156
|
+
<Sparkles className="mb-2 h-8 w-8" />
|
|
157
|
+
<p>{t('empty')}</p>
|
|
158
|
+
<Button
|
|
159
|
+
variant="outline"
|
|
160
|
+
size="sm"
|
|
161
|
+
className="mt-4"
|
|
162
|
+
onClick={handleGenerate}
|
|
163
|
+
>
|
|
164
|
+
<Plus className="mr-2 h-4 w-4" />
|
|
165
|
+
{t('actions.generate')}
|
|
166
|
+
</Button>
|
|
167
|
+
</div>
|
|
168
|
+
) : (
|
|
169
|
+
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-3">
|
|
170
|
+
{filtered.map((skill) => (
|
|
171
|
+
<SkillCard
|
|
172
|
+
key={skill.id}
|
|
173
|
+
skill={skill}
|
|
174
|
+
targets={targets}
|
|
175
|
+
onInstall={() => setInstallSkill(skill)}
|
|
176
|
+
onEdit={() => setEditSkill(skill)}
|
|
177
|
+
onToggle={() => handleToggle(skill.id)}
|
|
178
|
+
onDelete={() => handleDelete(skill.id)}
|
|
179
|
+
/>
|
|
180
|
+
))}
|
|
181
|
+
</div>
|
|
182
|
+
)}
|
|
183
|
+
|
|
184
|
+
{/* Install Dialog */}
|
|
185
|
+
{installSkill && (
|
|
186
|
+
<SkillInstallDialog
|
|
187
|
+
skill={installSkill}
|
|
188
|
+
targets={targets}
|
|
189
|
+
open={!!installSkill}
|
|
190
|
+
onOpenChange={(open) => { if (!open) setInstallSkill(null) }}
|
|
191
|
+
onDone={handleInstallDone}
|
|
192
|
+
/>
|
|
193
|
+
)}
|
|
194
|
+
|
|
195
|
+
{/* Editor Dialog */}
|
|
196
|
+
{editSkill && (
|
|
197
|
+
<SkillEditorDialog
|
|
198
|
+
skill={editSkill}
|
|
199
|
+
open={!!editSkill}
|
|
200
|
+
onOpenChange={(open) => { if (!open) setEditSkill(null) }}
|
|
201
|
+
onSave={handleEditSave}
|
|
202
|
+
/>
|
|
203
|
+
)}
|
|
204
|
+
</div>
|
|
205
|
+
)
|
|
206
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { api } from '@octo-cyber/ui/services/api-client'
|
|
2
|
+
import type { ApiResponse } from '@octo-cyber/ui/types/common'
|
|
3
|
+
|
|
4
|
+
export interface SkillInfo {
|
|
5
|
+
id: string
|
|
6
|
+
name: string
|
|
7
|
+
description: string
|
|
8
|
+
sourceModule: string
|
|
9
|
+
sourceType: 'module' | 'custom'
|
|
10
|
+
content: string
|
|
11
|
+
version: string
|
|
12
|
+
isEnabled: boolean
|
|
13
|
+
isEdited: boolean
|
|
14
|
+
installTargets: string | null
|
|
15
|
+
installedTargets: string[]
|
|
16
|
+
createdAt: string
|
|
17
|
+
updatedAt: string
|
|
18
|
+
lastInstalledAt: string | null
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface SkillTargetInfo {
|
|
22
|
+
id: string
|
|
23
|
+
name: string
|
|
24
|
+
skillsDir: string
|
|
25
|
+
detectDir: string
|
|
26
|
+
isInstalled: boolean
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface GenerateResult {
|
|
30
|
+
created: string[]
|
|
31
|
+
updated: string[]
|
|
32
|
+
skipped: string[]
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface InstallResult {
|
|
36
|
+
installed: string[]
|
|
37
|
+
failed: Array<{ target: string; error: string }>
|
|
38
|
+
apiKeyCreated: boolean
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export const skillService = {
|
|
42
|
+
async list(): Promise<SkillInfo[]> {
|
|
43
|
+
const res = await api.get<ApiResponse<SkillInfo[]>>('/api/v1/skills')
|
|
44
|
+
return res.data
|
|
45
|
+
},
|
|
46
|
+
|
|
47
|
+
async getById(id: string): Promise<SkillInfo> {
|
|
48
|
+
const res = await api.get<ApiResponse<SkillInfo>>(`/api/v1/skills/${id}`)
|
|
49
|
+
return res.data
|
|
50
|
+
},
|
|
51
|
+
|
|
52
|
+
async getTargets(): Promise<SkillTargetInfo[]> {
|
|
53
|
+
const res = await api.get<ApiResponse<SkillTargetInfo[]>>('/api/v1/skills/targets')
|
|
54
|
+
return res.data
|
|
55
|
+
},
|
|
56
|
+
|
|
57
|
+
async generate(baseUrl?: string): Promise<GenerateResult> {
|
|
58
|
+
const res = await api.post<ApiResponse<GenerateResult>>('/api/v1/skills/generate', { baseUrl })
|
|
59
|
+
return res.data
|
|
60
|
+
},
|
|
61
|
+
|
|
62
|
+
async create(data: { name: string; description: string; content: string }): Promise<SkillInfo> {
|
|
63
|
+
const res = await api.post<ApiResponse<SkillInfo>>('/api/v1/skills', data)
|
|
64
|
+
return res.data
|
|
65
|
+
},
|
|
66
|
+
|
|
67
|
+
async update(id: string, data: { name?: string; description?: string; content?: string }): Promise<SkillInfo> {
|
|
68
|
+
const res = await api.put<ApiResponse<SkillInfo>>(`/api/v1/skills/${id}`, data)
|
|
69
|
+
return res.data
|
|
70
|
+
},
|
|
71
|
+
|
|
72
|
+
async toggle(id: string): Promise<SkillInfo> {
|
|
73
|
+
const res = await api.put<ApiResponse<SkillInfo>>(`/api/v1/skills/${id}/toggle`)
|
|
74
|
+
return res.data
|
|
75
|
+
},
|
|
76
|
+
|
|
77
|
+
async deleteSkill(id: string): Promise<void> {
|
|
78
|
+
await api.delete<ApiResponse<null>>(`/api/v1/skills/${id}`)
|
|
79
|
+
},
|
|
80
|
+
|
|
81
|
+
async install(id: string, targets: string[], baseUrl?: string): Promise<InstallResult> {
|
|
82
|
+
const res = await api.post<ApiResponse<InstallResult>>(`/api/v1/skills/${id}/install`, { targets, baseUrl })
|
|
83
|
+
return res.data
|
|
84
|
+
},
|
|
85
|
+
|
|
86
|
+
async uninstall(id: string, targets: string[]): Promise<void> {
|
|
87
|
+
await api.post<ApiResponse<unknown>>(`/api/v1/skills/${id}/uninstall`, { targets })
|
|
88
|
+
},
|
|
89
|
+
}
|