@octo-cyber/ai 0.5.1 → 0.5.3

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.
Files changed (46) hide show
  1. package/dist/ai.module.d.ts +1 -0
  2. package/dist/ai.module.d.ts.map +1 -1
  3. package/dist/ai.module.js +21 -2
  4. package/dist/ai.module.js.map +1 -1
  5. package/dist/index.d.ts +1 -0
  6. package/dist/index.d.ts.map +1 -1
  7. package/dist/index.js +8 -1
  8. package/dist/index.js.map +1 -1
  9. package/dist/skill/controllers/skill.controller.d.ts +33 -0
  10. package/dist/skill/controllers/skill.controller.d.ts.map +1 -0
  11. package/dist/skill/controllers/skill.controller.js +264 -0
  12. package/dist/skill/controllers/skill.controller.js.map +1 -0
  13. package/dist/skill/entities/octo-skill.entity.d.ts +22 -0
  14. package/dist/skill/entities/octo-skill.entity.d.ts.map +1 -0
  15. package/dist/skill/entities/octo-skill.entity.js +91 -0
  16. package/dist/skill/entities/octo-skill.entity.js.map +1 -0
  17. package/dist/skill/index.d.ts +7 -0
  18. package/dist/skill/index.d.ts.map +1 -0
  19. package/dist/skill/index.js +17 -0
  20. package/dist/skill/index.js.map +1 -0
  21. package/dist/skill/services/skill-generator.service.d.ts +47 -0
  22. package/dist/skill/services/skill-generator.service.d.ts.map +1 -0
  23. package/dist/skill/services/skill-generator.service.js +232 -0
  24. package/dist/skill/services/skill-generator.service.js.map +1 -0
  25. package/dist/skill/services/skill-installer.service.d.ts +38 -0
  26. package/dist/skill/services/skill-installer.service.d.ts.map +1 -0
  27. package/dist/skill/services/skill-installer.service.js +150 -0
  28. package/dist/skill/services/skill-installer.service.js.map +1 -0
  29. package/dist/skill/services/skill-registry.service.d.ts +60 -0
  30. package/dist/skill/services/skill-registry.service.d.ts.map +1 -0
  31. package/dist/skill/services/skill-registry.service.js +147 -0
  32. package/dist/skill/services/skill-registry.service.js.map +1 -0
  33. package/dist/skill/types.d.ts +45 -0
  34. package/dist/skill/types.d.ts.map +1 -0
  35. package/dist/skill/types.js +6 -0
  36. package/dist/skill/types.js.map +1 -0
  37. package/package.json +4 -4
  38. package/web/components/SkillCard.tsx +139 -0
  39. package/web/components/SkillEditorDialog.tsx +77 -0
  40. package/web/components/SkillInstallDialog.tsx +134 -0
  41. package/web/index.ts +2 -0
  42. package/web/manifest.ts +5 -4
  43. package/web/messages/en-US.json +53 -0
  44. package/web/messages/zh-CN.json +53 -0
  45. package/web/pages/SkillsPage.tsx +206 -0
  46. 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
+ }