cognova 0.1.0
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/.env.example +58 -0
- package/Claude/CLAUDE.md +92 -0
- package/Claude/hooks/lib/__init__.py +1 -0
- package/Claude/hooks/lib/hook_client.py +207 -0
- package/Claude/hooks/log-event.py +97 -0
- package/Claude/hooks/pre-compact.py +46 -0
- package/Claude/hooks/session-end.py +26 -0
- package/Claude/hooks/session-start.py +35 -0
- package/Claude/hooks/stop-extract.py +40 -0
- package/Claude/rules/frontmatter.md +54 -0
- package/Claude/rules/markdown.md +43 -0
- package/Claude/rules/note-organization.md +33 -0
- package/Claude/settings.json +54 -0
- package/Claude/skills/README.md +136 -0
- package/Claude/skills/_lib/__init__.py +1 -0
- package/Claude/skills/_lib/api.py +164 -0
- package/Claude/skills/_lib/output.py +95 -0
- package/Claude/skills/environment/SKILL.md +73 -0
- package/Claude/skills/environment/environment.py +239 -0
- package/Claude/skills/memory/SKILL.md +153 -0
- package/Claude/skills/memory/memory.py +270 -0
- package/Claude/skills/project/SKILL.md +105 -0
- package/Claude/skills/project/project.py +203 -0
- package/Claude/skills/skill-creator/SKILL.md +261 -0
- package/Claude/skills/task/SKILL.md +135 -0
- package/Claude/skills/task/task.py +310 -0
- package/LICENSE +21 -0
- package/README.md +176 -0
- package/app/app.config.ts +8 -0
- package/app/app.vue +39 -0
- package/app/assets/css/main.css +10 -0
- package/app/components/AppLogo.vue +40 -0
- package/app/components/AssistantPanel.client.vue +518 -0
- package/app/components/ConfirmModal.vue +84 -0
- package/app/components/TemplateMenu.vue +49 -0
- package/app/components/agents/AgentActivityChart.client.vue +105 -0
- package/app/components/agents/AgentActivityChart.server.vue +25 -0
- package/app/components/agents/AgentForm.vue +304 -0
- package/app/components/agents/AgentRunModal.vue +154 -0
- package/app/components/agents/AgentStatsCards.vue +98 -0
- package/app/components/chat/ChatInput.vue +85 -0
- package/app/components/chat/ConversationList.vue +78 -0
- package/app/components/chat/MessageBubble.vue +81 -0
- package/app/components/chat/StreamingMessage.vue +36 -0
- package/app/components/chat/ToolCallBlock.vue +77 -0
- package/app/components/editor/CodeEditor.client.vue +212 -0
- package/app/components/editor/CodeEditorFallback.vue +12 -0
- package/app/components/editor/DocumentEditor.vue +326 -0
- package/app/components/editor/DocumentMetadata.vue +140 -0
- package/app/components/editor/MarkdownEditor.vue +146 -0
- package/app/components/files/FileTree.vue +436 -0
- package/app/components/hooks/HookActivityChart.client.vue +117 -0
- package/app/components/hooks/HookActivityChart.server.vue +25 -0
- package/app/components/hooks/HookStatsCards.vue +63 -0
- package/app/components/hooks/RecentEventsTable.vue +123 -0
- package/app/components/hooks/ToolBreakdownTable.vue +72 -0
- package/app/components/search/DashboardSearch.vue +122 -0
- package/app/components/tasks/ProjectSelect.vue +35 -0
- package/app/components/tasks/TaskCard.vue +182 -0
- package/app/components/tasks/TaskDetail.vue +160 -0
- package/app/components/tasks/TaskForm.vue +280 -0
- package/app/components/tasks/TaskList.vue +69 -0
- package/app/components/view/ViewToc.vue +85 -0
- package/app/composables/useAgents.ts +153 -0
- package/app/composables/useAuth.ts +73 -0
- package/app/composables/useChat.ts +298 -0
- package/app/composables/useDocument.ts +141 -0
- package/app/composables/useEditor.ts +100 -0
- package/app/composables/useFileTree.ts +220 -0
- package/app/composables/useHookEvents.ts +68 -0
- package/app/composables/useMemories.ts +83 -0
- package/app/composables/useNotificationBus.ts +154 -0
- package/app/composables/usePreferences.ts +131 -0
- package/app/composables/useProjects.ts +97 -0
- package/app/composables/useSearch.ts +52 -0
- package/app/composables/useTasks.ts +201 -0
- package/app/composables/useTerminal.ts +135 -0
- package/app/layouts/auth.vue +20 -0
- package/app/layouts/dashboard.vue +186 -0
- package/app/layouts/view.vue +60 -0
- package/app/middleware/auth.ts +9 -0
- package/app/pages/agents/[id].vue +602 -0
- package/app/pages/agents/index.vue +412 -0
- package/app/pages/chat.vue +146 -0
- package/app/pages/dashboard.vue +80 -0
- package/app/pages/docs.vue +131 -0
- package/app/pages/hooks.vue +163 -0
- package/app/pages/index.vue +249 -0
- package/app/pages/login.vue +60 -0
- package/app/pages/memories.vue +282 -0
- package/app/pages/settings.vue +625 -0
- package/app/pages/tasks.vue +312 -0
- package/app/pages/view/[uuid].vue +376 -0
- package/dist/cli/index.js +2711 -0
- package/drizzle.config.ts +10 -0
- package/nuxt.config.ts +98 -0
- package/package.json +107 -0
- package/server/api/agents/[id]/cancel.post.ts +27 -0
- package/server/api/agents/[id]/run.post.ts +34 -0
- package/server/api/agents/[id]/runs.get.ts +45 -0
- package/server/api/agents/[id]/stats.get.ts +94 -0
- package/server/api/agents/[id].delete.ts +29 -0
- package/server/api/agents/[id].get.ts +25 -0
- package/server/api/agents/[id].patch.ts +55 -0
- package/server/api/agents/index.get.ts +15 -0
- package/server/api/agents/index.post.ts +48 -0
- package/server/api/agents/stats.get.ts +86 -0
- package/server/api/auth/[...all].ts +5 -0
- package/server/api/conversations/[id].delete.ts +16 -0
- package/server/api/conversations/[id].get.ts +34 -0
- package/server/api/conversations/index.get.ts +17 -0
- package/server/api/documents/[id]/index.delete.ts +47 -0
- package/server/api/documents/[id]/index.put.ts +102 -0
- package/server/api/documents/[id]/public.get.ts +60 -0
- package/server/api/documents/[id]/restore.post.ts +65 -0
- package/server/api/documents/by-path.post.ts +168 -0
- package/server/api/documents/index.get.ts +48 -0
- package/server/api/fs/delete.post.ts +41 -0
- package/server/api/fs/list.get.ts +99 -0
- package/server/api/fs/mkdir.post.ts +44 -0
- package/server/api/fs/move.post.ts +68 -0
- package/server/api/fs/read.post.ts +48 -0
- package/server/api/fs/rename.post.ts +55 -0
- package/server/api/fs/write.post.ts +51 -0
- package/server/api/health.get.ts +40 -0
- package/server/api/home.get.ts +26 -0
- package/server/api/hooks/events/index.get.ts +56 -0
- package/server/api/hooks/events/index.post.ts +36 -0
- package/server/api/hooks/stats.get.ts +99 -0
- package/server/api/memory/[id].delete.ts +26 -0
- package/server/api/memory/context.get.ts +83 -0
- package/server/api/memory/extract.post.ts +42 -0
- package/server/api/memory/search.get.ts +70 -0
- package/server/api/memory/store.post.ts +31 -0
- package/server/api/projects/[id]/index.delete.ts +40 -0
- package/server/api/projects/[id]/index.get.ts +25 -0
- package/server/api/projects/[id]/index.put.ts +50 -0
- package/server/api/projects/index.get.ts +20 -0
- package/server/api/projects/index.post.ts +34 -0
- package/server/api/secrets/[key].delete.ts +31 -0
- package/server/api/secrets/[key].get.ts +30 -0
- package/server/api/secrets/[key].put.ts +52 -0
- package/server/api/secrets/index.get.ts +20 -0
- package/server/api/secrets/index.post.ts +58 -0
- package/server/api/tasks/[id]/index.delete.ts +46 -0
- package/server/api/tasks/[id]/index.get.ts +24 -0
- package/server/api/tasks/[id]/index.put.ts +70 -0
- package/server/api/tasks/[id]/restore.post.ts +49 -0
- package/server/api/tasks/index.get.ts +53 -0
- package/server/api/tasks/index.post.ts +47 -0
- package/server/api/tasks/tags.get.ts +21 -0
- package/server/api/user/email.patch.ts +56 -0
- package/server/db/index.ts +76 -0
- package/server/db/migrate.ts +41 -0
- package/server/db/schema.ts +345 -0
- package/server/db/seed.ts +46 -0
- package/server/db/types.ts +28 -0
- package/server/drizzle/migrations/0000_brown_george_stacy.sql +34 -0
- package/server/drizzle/migrations/0001_stormy_pyro.sql +16 -0
- package/server/drizzle/migrations/0002_clean_colossus.sql +50 -0
- package/server/drizzle/migrations/0003_fine_joystick.sql +12 -0
- package/server/drizzle/migrations/0004_tan_groot.sql +26 -0
- package/server/drizzle/migrations/0005_cloudy_lilith.sql +33 -0
- package/server/drizzle/migrations/0006_ordinary_retro_girl.sql +13 -0
- package/server/drizzle/migrations/0007_flowery_venus.sql +15 -0
- package/server/drizzle/migrations/0008_talented_zombie.sql +13 -0
- package/server/drizzle/migrations/0009_gray_shen.sql +15 -0
- package/server/drizzle/migrations/meta/0000_snapshot.json +230 -0
- package/server/drizzle/migrations/meta/0001_snapshot.json +306 -0
- package/server/drizzle/migrations/meta/0002_snapshot.json +615 -0
- package/server/drizzle/migrations/meta/0003_snapshot.json +730 -0
- package/server/drizzle/migrations/meta/0004_snapshot.json +916 -0
- package/server/drizzle/migrations/meta/0005_snapshot.json +1127 -0
- package/server/drizzle/migrations/meta/0006_snapshot.json +1213 -0
- package/server/drizzle/migrations/meta/0007_snapshot.json +1307 -0
- package/server/drizzle/migrations/meta/0008_snapshot.json +1390 -0
- package/server/drizzle/migrations/meta/0009_snapshot.json +1487 -0
- package/server/drizzle/migrations/meta/_journal.json +76 -0
- package/server/middleware/auth.ts +79 -0
- package/server/plugins/00.env-validate.ts +38 -0
- package/server/plugins/01.api-token.ts +31 -0
- package/server/plugins/02.database.ts +54 -0
- package/server/plugins/03.file-watcher.ts +65 -0
- package/server/plugins/04.cron-agents.ts +26 -0
- package/server/routes/_ws/chat.ts +252 -0
- package/server/routes/notifications.ts +47 -0
- package/server/routes/terminal.ts +98 -0
- package/server/services/agent-executor.ts +218 -0
- package/server/services/cron-scheduler.ts +78 -0
- package/server/services/memory-extractor.ts +120 -0
- package/server/utils/agent-cleanup.ts +91 -0
- package/server/utils/agent-registry.ts +95 -0
- package/server/utils/auth.ts +33 -0
- package/server/utils/chat-session-manager.ts +59 -0
- package/server/utils/crypto.ts +40 -0
- package/server/utils/db-guard.ts +12 -0
- package/server/utils/db-state.ts +63 -0
- package/server/utils/document-sync.ts +207 -0
- package/server/utils/frontmatter.ts +84 -0
- package/server/utils/notification-bus.ts +60 -0
- package/server/utils/path-validator.ts +55 -0
- package/server/utils/pty-manager.ts +130 -0
- package/shared/types/index.ts +604 -0
- package/shared/utils/language-detection.ts +87 -0
- package/tsconfig.json +10 -0
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
import type { FileEntry } from '~/../../shared/types'
|
|
2
|
+
|
|
3
|
+
export interface TreeItem {
|
|
4
|
+
id: string
|
|
5
|
+
label: string
|
|
6
|
+
icon?: string
|
|
7
|
+
path: string
|
|
8
|
+
type: 'file' | 'directory'
|
|
9
|
+
children?: TreeItem[]
|
|
10
|
+
defaultExpanded?: boolean
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function useFileTree() {
|
|
14
|
+
const items = ref<TreeItem[]>([])
|
|
15
|
+
const selectedFile = ref<string | null>(null)
|
|
16
|
+
const loading = ref(false)
|
|
17
|
+
const error = ref<string | null>(null)
|
|
18
|
+
const searchQuery = ref('')
|
|
19
|
+
|
|
20
|
+
function fileEntryToTreeItem(entry: FileEntry, expandRoot = false): TreeItem {
|
|
21
|
+
const icon = entry.type === 'directory'
|
|
22
|
+
? undefined
|
|
23
|
+
: getFileIcon(entry.name)
|
|
24
|
+
|
|
25
|
+
return {
|
|
26
|
+
id: entry.path,
|
|
27
|
+
label: entry.name,
|
|
28
|
+
icon,
|
|
29
|
+
path: entry.path,
|
|
30
|
+
type: entry.type,
|
|
31
|
+
defaultExpanded: expandRoot,
|
|
32
|
+
children: entry.children?.map(child => fileEntryToTreeItem(child))
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function getFileIcon(filename: string): string {
|
|
37
|
+
const ext = filename.split('.').pop()?.toLowerCase()
|
|
38
|
+
switch (ext) {
|
|
39
|
+
case 'md':
|
|
40
|
+
return 'i-lucide-file-text'
|
|
41
|
+
case 'ts':
|
|
42
|
+
case 'tsx':
|
|
43
|
+
return 'i-lucide-file-code'
|
|
44
|
+
case 'js':
|
|
45
|
+
case 'jsx':
|
|
46
|
+
return 'i-lucide-file-code'
|
|
47
|
+
case 'vue':
|
|
48
|
+
return 'i-lucide-file-code'
|
|
49
|
+
case 'json':
|
|
50
|
+
return 'i-lucide-file-json'
|
|
51
|
+
case 'css':
|
|
52
|
+
case 'scss':
|
|
53
|
+
return 'i-lucide-file-code'
|
|
54
|
+
case 'png':
|
|
55
|
+
case 'jpg':
|
|
56
|
+
case 'jpeg':
|
|
57
|
+
case 'gif':
|
|
58
|
+
case 'svg':
|
|
59
|
+
return 'i-lucide-file-image'
|
|
60
|
+
default:
|
|
61
|
+
return 'i-lucide-file'
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
async function loadTree(path = '/') {
|
|
66
|
+
loading.value = true
|
|
67
|
+
error.value = null
|
|
68
|
+
|
|
69
|
+
try {
|
|
70
|
+
const response = await $fetch<{ data: FileEntry[] }>('/api/fs/list', {
|
|
71
|
+
query: { path, recursive: 'true' }
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
items.value = response.data.map(entry => fileEntryToTreeItem(entry, true))
|
|
75
|
+
} catch (e) {
|
|
76
|
+
error.value = 'Failed to load file tree'
|
|
77
|
+
console.error('Failed to load file tree:', e)
|
|
78
|
+
} finally {
|
|
79
|
+
loading.value = false
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
async function readFile(path: string) {
|
|
84
|
+
try {
|
|
85
|
+
const response = await $fetch<{ data: { content: string, path: string, modifiedAt: string } }>('/api/fs/read', {
|
|
86
|
+
method: 'POST',
|
|
87
|
+
body: { path }
|
|
88
|
+
})
|
|
89
|
+
return response.data
|
|
90
|
+
} catch (e) {
|
|
91
|
+
console.error('Failed to read file:', e)
|
|
92
|
+
throw e
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
async function writeFile(path: string, content: string) {
|
|
97
|
+
try {
|
|
98
|
+
const response = await $fetch<{ data: { path: string, created: boolean } }>('/api/fs/write', {
|
|
99
|
+
method: 'POST',
|
|
100
|
+
body: { path, content }
|
|
101
|
+
})
|
|
102
|
+
return response.data
|
|
103
|
+
} catch (e) {
|
|
104
|
+
console.error('Failed to write file:', e)
|
|
105
|
+
throw e
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
async function createFile(parentPath: string, filename: string) {
|
|
110
|
+
const path = parentPath === '/' ? `/${filename}` : `${parentPath}/${filename}`
|
|
111
|
+
await writeFile(path, '')
|
|
112
|
+
await loadTree()
|
|
113
|
+
return path
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
async function createFolder(parentPath: string, foldername: string) {
|
|
117
|
+
const path = parentPath === '/' ? `/${foldername}` : `${parentPath}/${foldername}`
|
|
118
|
+
try {
|
|
119
|
+
await $fetch('/api/fs/mkdir', {
|
|
120
|
+
method: 'POST',
|
|
121
|
+
body: { path }
|
|
122
|
+
})
|
|
123
|
+
await loadTree()
|
|
124
|
+
return path
|
|
125
|
+
} catch (e) {
|
|
126
|
+
console.error('Failed to create folder:', e)
|
|
127
|
+
throw e
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
async function renameItem(oldPath: string, newName: string) {
|
|
132
|
+
const parts = oldPath.split('/')
|
|
133
|
+
parts.pop()
|
|
134
|
+
const newPath = [...parts, newName].join('/')
|
|
135
|
+
|
|
136
|
+
try {
|
|
137
|
+
await $fetch('/api/fs/rename', {
|
|
138
|
+
method: 'POST',
|
|
139
|
+
body: { oldPath, newPath }
|
|
140
|
+
})
|
|
141
|
+
await loadTree()
|
|
142
|
+
return newPath
|
|
143
|
+
} catch (e) {
|
|
144
|
+
console.error('Failed to rename:', e)
|
|
145
|
+
throw e
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
async function deleteItem(path: string) {
|
|
150
|
+
try {
|
|
151
|
+
await $fetch('/api/fs/delete', {
|
|
152
|
+
method: 'POST',
|
|
153
|
+
body: { path }
|
|
154
|
+
})
|
|
155
|
+
if (selectedFile.value === path) {
|
|
156
|
+
selectedFile.value = null
|
|
157
|
+
}
|
|
158
|
+
await loadTree()
|
|
159
|
+
} catch (e) {
|
|
160
|
+
console.error('Failed to delete:', e)
|
|
161
|
+
throw e
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
async function moveItem(sourcePath: string, destinationPath: string) {
|
|
166
|
+
try {
|
|
167
|
+
const response = await $fetch<{ data: { oldPath: string, newPath: string, moved: boolean } }>('/api/fs/move', {
|
|
168
|
+
method: 'POST',
|
|
169
|
+
body: { sourcePath, destinationPath }
|
|
170
|
+
})
|
|
171
|
+
await loadTree()
|
|
172
|
+
return response.data.newPath
|
|
173
|
+
} catch (e) {
|
|
174
|
+
console.error('Failed to move:', e)
|
|
175
|
+
throw e
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const filteredItems = computed(() => {
|
|
180
|
+
if (!searchQuery.value) return items.value
|
|
181
|
+
|
|
182
|
+
const query = searchQuery.value.toLowerCase()
|
|
183
|
+
|
|
184
|
+
function filterTree(items: TreeItem[]): TreeItem[] {
|
|
185
|
+
return items.reduce<TreeItem[]>((acc, item) => {
|
|
186
|
+
const matches = item.label.toLowerCase().includes(query)
|
|
187
|
+
const filteredChildren = item.children ? filterTree(item.children) : []
|
|
188
|
+
|
|
189
|
+
if (matches || filteredChildren.length > 0) {
|
|
190
|
+
acc.push({
|
|
191
|
+
...item,
|
|
192
|
+
children: filteredChildren.length > 0 ? filteredChildren : item.children,
|
|
193
|
+
defaultExpanded: filteredChildren.length > 0 || matches
|
|
194
|
+
})
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
return acc
|
|
198
|
+
}, [])
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
return filterTree(items.value)
|
|
202
|
+
})
|
|
203
|
+
|
|
204
|
+
return {
|
|
205
|
+
items,
|
|
206
|
+
filteredItems,
|
|
207
|
+
selectedFile,
|
|
208
|
+
loading,
|
|
209
|
+
error,
|
|
210
|
+
searchQuery,
|
|
211
|
+
loadTree,
|
|
212
|
+
readFile,
|
|
213
|
+
writeFile,
|
|
214
|
+
createFile,
|
|
215
|
+
createFolder,
|
|
216
|
+
renameItem,
|
|
217
|
+
deleteItem,
|
|
218
|
+
moveItem
|
|
219
|
+
}
|
|
220
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import type { HookEvent, HookEventStats, HookEventFilters, StatsPeriod } from '~~/shared/types'
|
|
2
|
+
|
|
3
|
+
export function useHookEvents() {
|
|
4
|
+
const events = ref<HookEvent[]>([])
|
|
5
|
+
const stats = ref<HookEventStats | null>(null)
|
|
6
|
+
const loading = ref(false)
|
|
7
|
+
const error = ref<string | null>(null)
|
|
8
|
+
|
|
9
|
+
async function fetchEvents(filters?: HookEventFilters) {
|
|
10
|
+
loading.value = true
|
|
11
|
+
error.value = null
|
|
12
|
+
|
|
13
|
+
try {
|
|
14
|
+
const response = await $fetch<{ data: HookEvent[] }>('/api/hooks/events', {
|
|
15
|
+
query: filters
|
|
16
|
+
})
|
|
17
|
+
events.value = response.data
|
|
18
|
+
return response.data
|
|
19
|
+
} catch (e) {
|
|
20
|
+
error.value = 'Failed to load hook events'
|
|
21
|
+
console.error('Failed to load hook events:', e)
|
|
22
|
+
throw e
|
|
23
|
+
} finally {
|
|
24
|
+
loading.value = false
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async function fetchStats(period: StatsPeriod = '7d') {
|
|
29
|
+
loading.value = true
|
|
30
|
+
error.value = null
|
|
31
|
+
|
|
32
|
+
try {
|
|
33
|
+
const response = await $fetch<{ data: HookEventStats }>('/api/hooks/stats', {
|
|
34
|
+
query: { period }
|
|
35
|
+
})
|
|
36
|
+
stats.value = response.data
|
|
37
|
+
return response.data
|
|
38
|
+
} catch (e) {
|
|
39
|
+
error.value = 'Failed to load hook stats'
|
|
40
|
+
console.error('Failed to load hook stats:', e)
|
|
41
|
+
throw e
|
|
42
|
+
} finally {
|
|
43
|
+
loading.value = false
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async function fetchEventsBySession(sessionId: string, limit = 100) {
|
|
48
|
+
try {
|
|
49
|
+
const response = await $fetch<{ data: HookEvent[] }>('/api/hooks/events', {
|
|
50
|
+
query: { sessionId, limit }
|
|
51
|
+
})
|
|
52
|
+
return response.data
|
|
53
|
+
} catch (e) {
|
|
54
|
+
console.error('Failed to fetch session events:', e)
|
|
55
|
+
throw e
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return {
|
|
60
|
+
events,
|
|
61
|
+
stats,
|
|
62
|
+
loading,
|
|
63
|
+
error,
|
|
64
|
+
fetchEvents,
|
|
65
|
+
fetchStats,
|
|
66
|
+
fetchEventsBySession
|
|
67
|
+
}
|
|
68
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import type { MemoryChunk, MemoryChunkType } from '~~/shared/types'
|
|
2
|
+
|
|
3
|
+
interface MemoryFilters {
|
|
4
|
+
query?: string
|
|
5
|
+
chunkType?: MemoryChunkType | 'all'
|
|
6
|
+
projectPath?: string
|
|
7
|
+
limit?: number
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function useMemories() {
|
|
11
|
+
const memories = ref<MemoryChunk[]>([])
|
|
12
|
+
const loading = ref(true) // Start true to avoid SSR rendering issues
|
|
13
|
+
const error = ref<string | null>(null)
|
|
14
|
+
|
|
15
|
+
const filters = reactive<MemoryFilters>({
|
|
16
|
+
query: '',
|
|
17
|
+
chunkType: 'all',
|
|
18
|
+
limit: 50
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
async function fetchMemories(customFilters?: Partial<MemoryFilters>) {
|
|
22
|
+
loading.value = true
|
|
23
|
+
error.value = null
|
|
24
|
+
|
|
25
|
+
const queryFilters = { ...filters, ...customFilters }
|
|
26
|
+
|
|
27
|
+
try {
|
|
28
|
+
const query: Record<string, string> = {}
|
|
29
|
+
|
|
30
|
+
if (queryFilters.query)
|
|
31
|
+
query.query = queryFilters.query
|
|
32
|
+
if (queryFilters.chunkType && queryFilters.chunkType !== 'all')
|
|
33
|
+
query.chunkType = queryFilters.chunkType
|
|
34
|
+
if (queryFilters.projectPath)
|
|
35
|
+
query.projectPath = queryFilters.projectPath
|
|
36
|
+
if (queryFilters.limit)
|
|
37
|
+
query.limit = String(queryFilters.limit)
|
|
38
|
+
|
|
39
|
+
const response = await $fetch<{ data: MemoryChunk[] }>('/api/memory/search', { query })
|
|
40
|
+
memories.value = response.data
|
|
41
|
+
return response.data
|
|
42
|
+
} catch (e) {
|
|
43
|
+
error.value = 'Failed to load memories'
|
|
44
|
+
console.error('Failed to load memories:', e)
|
|
45
|
+
throw e
|
|
46
|
+
} finally {
|
|
47
|
+
loading.value = false
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
async function deleteMemory(id: string) {
|
|
52
|
+
try {
|
|
53
|
+
await $fetch(`/api/memory/${id}`, { method: 'DELETE' })
|
|
54
|
+
memories.value = memories.value.filter(m => m.id !== id)
|
|
55
|
+
} catch (e) {
|
|
56
|
+
console.error('Failed to delete memory:', e)
|
|
57
|
+
throw e
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Computed stats
|
|
62
|
+
const stats = computed(() => {
|
|
63
|
+
const byType = memories.value.reduce((acc, m) => {
|
|
64
|
+
acc[m.chunkType] = (acc[m.chunkType] || 0) + 1
|
|
65
|
+
return acc
|
|
66
|
+
}, {} as Record<string, number>)
|
|
67
|
+
|
|
68
|
+
return {
|
|
69
|
+
total: memories.value.length,
|
|
70
|
+
byType
|
|
71
|
+
}
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
return {
|
|
75
|
+
memories,
|
|
76
|
+
loading,
|
|
77
|
+
error,
|
|
78
|
+
filters,
|
|
79
|
+
stats,
|
|
80
|
+
fetchMemories,
|
|
81
|
+
deleteMemory
|
|
82
|
+
}
|
|
83
|
+
}
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import type { NotificationPayload } from '~~/shared/types'
|
|
2
|
+
|
|
3
|
+
export type NotificationBusStatus = 'disconnected' | 'connecting' | 'connected' | 'error'
|
|
4
|
+
|
|
5
|
+
// Shared state across all component instances
|
|
6
|
+
const status = ref<NotificationBusStatus>('disconnected')
|
|
7
|
+
const runningAgentIds = ref<Set<string>>(new Set())
|
|
8
|
+
const ws = ref<WebSocket | null>(null)
|
|
9
|
+
const reconnectAttempts = ref(0)
|
|
10
|
+
const maxReconnectAttempts = 5
|
|
11
|
+
const reconnectDelay = 2000
|
|
12
|
+
let pingInterval: ReturnType<typeof setInterval> | null = null
|
|
13
|
+
let isInitialized = false
|
|
14
|
+
|
|
15
|
+
export function useNotificationBus() {
|
|
16
|
+
const toast = useToast()
|
|
17
|
+
|
|
18
|
+
function getWebSocketUrl(): string {
|
|
19
|
+
if (import.meta.server) return ''
|
|
20
|
+
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:'
|
|
21
|
+
return `${protocol}//${window.location.host}/notifications`
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function handleNotification(payload: NotificationPayload) {
|
|
25
|
+
// Update running agents state
|
|
26
|
+
if (payload.type === 'agent:started' && payload.agentId) {
|
|
27
|
+
runningAgentIds.value.add(payload.agentId)
|
|
28
|
+
} else if ((payload.type === 'agent:completed' || payload.type === 'agent:failed') && payload.agentId) {
|
|
29
|
+
runningAgentIds.value.delete(payload.agentId)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Show toast for agent events
|
|
33
|
+
if (payload.type === 'agent:started') {
|
|
34
|
+
toast.add({
|
|
35
|
+
title: 'Agent Started',
|
|
36
|
+
description: payload.agentName || 'An agent has started running',
|
|
37
|
+
icon: 'i-lucide-play',
|
|
38
|
+
color: 'info'
|
|
39
|
+
})
|
|
40
|
+
} else if (payload.type === 'agent:completed') {
|
|
41
|
+
toast.add({
|
|
42
|
+
title: 'Agent Completed',
|
|
43
|
+
description: payload.agentName || 'Agent run completed successfully',
|
|
44
|
+
icon: 'i-lucide-check-circle',
|
|
45
|
+
color: 'success'
|
|
46
|
+
})
|
|
47
|
+
} else if (payload.type === 'agent:failed') {
|
|
48
|
+
toast.add({
|
|
49
|
+
title: 'Agent Failed',
|
|
50
|
+
description: payload.message || payload.agentName || 'Agent run failed',
|
|
51
|
+
icon: 'i-lucide-alert-circle',
|
|
52
|
+
color: 'error'
|
|
53
|
+
})
|
|
54
|
+
} else if (payload.type === 'toast' && payload.title) {
|
|
55
|
+
toast.add({
|
|
56
|
+
title: payload.title,
|
|
57
|
+
description: payload.message,
|
|
58
|
+
color: payload.color || 'info'
|
|
59
|
+
})
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function connect() {
|
|
64
|
+
if (import.meta.server) return
|
|
65
|
+
if (ws.value?.readyState === WebSocket.OPEN) return
|
|
66
|
+
if (isInitialized && status.value === 'connecting') return
|
|
67
|
+
|
|
68
|
+
isInitialized = true
|
|
69
|
+
status.value = 'connecting'
|
|
70
|
+
|
|
71
|
+
const socket = new WebSocket(getWebSocketUrl())
|
|
72
|
+
ws.value = socket
|
|
73
|
+
|
|
74
|
+
socket.onopen = () => {
|
|
75
|
+
status.value = 'connected'
|
|
76
|
+
reconnectAttempts.value = 0
|
|
77
|
+
console.log('[notification-bus] Connected')
|
|
78
|
+
startPingInterval()
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
socket.onmessage = (event) => {
|
|
82
|
+
try {
|
|
83
|
+
const data = JSON.parse(event.data) as Record<string, unknown>
|
|
84
|
+
const msgType = data.type as string
|
|
85
|
+
|
|
86
|
+
// Skip pong and connected messages
|
|
87
|
+
if (msgType === 'pong' || msgType === 'connected') return
|
|
88
|
+
|
|
89
|
+
// Only handle known notification types
|
|
90
|
+
if (msgType === 'agent:started' || msgType === 'agent:completed' || msgType === 'agent:failed' || msgType === 'toast') {
|
|
91
|
+
handleNotification(data as unknown as NotificationPayload)
|
|
92
|
+
}
|
|
93
|
+
} catch (e) {
|
|
94
|
+
console.error('[notification-bus] Failed to parse message:', e)
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
socket.onclose = () => {
|
|
99
|
+
status.value = 'disconnected'
|
|
100
|
+
stopPingInterval()
|
|
101
|
+
|
|
102
|
+
// Attempt reconnection
|
|
103
|
+
if (reconnectAttempts.value < maxReconnectAttempts) {
|
|
104
|
+
reconnectAttempts.value++
|
|
105
|
+
console.log(`[notification-bus] Reconnecting (attempt ${reconnectAttempts.value})...`)
|
|
106
|
+
setTimeout(connect, reconnectDelay * reconnectAttempts.value)
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
socket.onerror = () => {
|
|
111
|
+
status.value = 'error'
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
function disconnect() {
|
|
116
|
+
stopPingInterval()
|
|
117
|
+
if (ws.value) {
|
|
118
|
+
ws.value.close()
|
|
119
|
+
ws.value = null
|
|
120
|
+
}
|
|
121
|
+
status.value = 'disconnected'
|
|
122
|
+
isInitialized = false
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function sendPing() {
|
|
126
|
+
if (ws.value?.readyState === WebSocket.OPEN) {
|
|
127
|
+
ws.value.send(JSON.stringify({ type: 'ping' }))
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function startPingInterval() {
|
|
132
|
+
if (pingInterval) clearInterval(pingInterval)
|
|
133
|
+
pingInterval = setInterval(sendPing, 30000)
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function stopPingInterval() {
|
|
137
|
+
if (pingInterval) {
|
|
138
|
+
clearInterval(pingInterval)
|
|
139
|
+
pingInterval = null
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function isAgentRunning(agentId: string): boolean {
|
|
144
|
+
return runningAgentIds.value.has(agentId)
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return {
|
|
148
|
+
status: readonly(status),
|
|
149
|
+
runningAgentIds: readonly(runningAgentIds),
|
|
150
|
+
isAgentRunning,
|
|
151
|
+
connect,
|
|
152
|
+
disconnect
|
|
153
|
+
}
|
|
154
|
+
}
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import type { TaskStatus } from '~~/shared/types'
|
|
2
|
+
|
|
3
|
+
interface Preferences {
|
|
4
|
+
// Editor preferences
|
|
5
|
+
editorMode: 'editor' | 'code'
|
|
6
|
+
viewSourceMode: boolean
|
|
7
|
+
|
|
8
|
+
// Navigation
|
|
9
|
+
lastDocumentPath: string | null
|
|
10
|
+
|
|
11
|
+
// Layout
|
|
12
|
+
sidebarOpen: boolean
|
|
13
|
+
docsTreeWidth: number
|
|
14
|
+
assistantPanelOpen: boolean
|
|
15
|
+
assistantPanelTab: 'chat' | 'terminal'
|
|
16
|
+
assistantLastConversationId: string | null
|
|
17
|
+
|
|
18
|
+
// Task filters
|
|
19
|
+
taskStatusFilter: TaskStatus | 'all'
|
|
20
|
+
taskProjectFilter: string | null
|
|
21
|
+
|
|
22
|
+
// Agent stats
|
|
23
|
+
agentStatsPeriod: '24h' | '7d' | '30d'
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const COOKIE_NAME = 'sb-preferences'
|
|
27
|
+
const COOKIE_MAX_AGE = 60 * 60 * 24 * 365 // 1 year
|
|
28
|
+
|
|
29
|
+
const defaults: Preferences = {
|
|
30
|
+
editorMode: 'editor',
|
|
31
|
+
viewSourceMode: false,
|
|
32
|
+
lastDocumentPath: null,
|
|
33
|
+
sidebarOpen: true,
|
|
34
|
+
docsTreeWidth: 16,
|
|
35
|
+
assistantPanelOpen: false,
|
|
36
|
+
assistantPanelTab: 'chat',
|
|
37
|
+
assistantLastConversationId: null,
|
|
38
|
+
taskStatusFilter: 'all',
|
|
39
|
+
taskProjectFilter: null,
|
|
40
|
+
agentStatsPeriod: '7d'
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function usePreferences() {
|
|
44
|
+
const cookie = useCookie<Partial<Preferences>>(COOKIE_NAME, {
|
|
45
|
+
maxAge: COOKIE_MAX_AGE,
|
|
46
|
+
default: () => ({})
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
function get<K extends keyof Preferences>(key: K): Preferences[K] {
|
|
50
|
+
return cookie.value[key] ?? defaults[key]
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function set<K extends keyof Preferences>(key: K, value: Preferences[K]) {
|
|
54
|
+
cookie.value = { ...cookie.value, [key]: value }
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Convenience computed refs for common preferences
|
|
58
|
+
const editorMode = computed({
|
|
59
|
+
get: () => get('editorMode'),
|
|
60
|
+
set: v => set('editorMode', v)
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
const viewSourceMode = computed({
|
|
64
|
+
get: () => get('viewSourceMode'),
|
|
65
|
+
set: v => set('viewSourceMode', v)
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
const lastDocumentPath = computed({
|
|
69
|
+
get: () => get('lastDocumentPath'),
|
|
70
|
+
set: v => set('lastDocumentPath', v)
|
|
71
|
+
})
|
|
72
|
+
|
|
73
|
+
const sidebarOpen = computed({
|
|
74
|
+
get: () => get('sidebarOpen'),
|
|
75
|
+
set: v => set('sidebarOpen', v)
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
const docsTreeWidth = computed({
|
|
79
|
+
get: () => get('docsTreeWidth'),
|
|
80
|
+
set: v => set('docsTreeWidth', v)
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
const taskStatusFilter = computed({
|
|
84
|
+
get: () => get('taskStatusFilter'),
|
|
85
|
+
set: v => set('taskStatusFilter', v)
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
const taskProjectFilter = computed({
|
|
89
|
+
get: () => get('taskProjectFilter'),
|
|
90
|
+
set: v => set('taskProjectFilter', v)
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
const agentStatsPeriod = computed({
|
|
94
|
+
get: () => get('agentStatsPeriod'),
|
|
95
|
+
set: v => set('agentStatsPeriod', v)
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
const assistantPanelOpen = computed({
|
|
99
|
+
get: () => get('assistantPanelOpen'),
|
|
100
|
+
set: v => set('assistantPanelOpen', v)
|
|
101
|
+
})
|
|
102
|
+
|
|
103
|
+
const assistantPanelTab = computed({
|
|
104
|
+
get: () => get('assistantPanelTab'),
|
|
105
|
+
set: v => set('assistantPanelTab', v)
|
|
106
|
+
})
|
|
107
|
+
|
|
108
|
+
const assistantLastConversationId = computed({
|
|
109
|
+
get: () => get('assistantLastConversationId'),
|
|
110
|
+
set: v => set('assistantLastConversationId', v)
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
return {
|
|
114
|
+
// Raw access
|
|
115
|
+
get,
|
|
116
|
+
set,
|
|
117
|
+
|
|
118
|
+
// Computed refs
|
|
119
|
+
editorMode,
|
|
120
|
+
viewSourceMode,
|
|
121
|
+
lastDocumentPath,
|
|
122
|
+
sidebarOpen,
|
|
123
|
+
docsTreeWidth,
|
|
124
|
+
assistantPanelOpen,
|
|
125
|
+
assistantPanelTab,
|
|
126
|
+
assistantLastConversationId,
|
|
127
|
+
taskStatusFilter,
|
|
128
|
+
taskProjectFilter,
|
|
129
|
+
agentStatsPeriod
|
|
130
|
+
}
|
|
131
|
+
}
|