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.
Files changed (205) hide show
  1. package/.env.example +58 -0
  2. package/Claude/CLAUDE.md +92 -0
  3. package/Claude/hooks/lib/__init__.py +1 -0
  4. package/Claude/hooks/lib/hook_client.py +207 -0
  5. package/Claude/hooks/log-event.py +97 -0
  6. package/Claude/hooks/pre-compact.py +46 -0
  7. package/Claude/hooks/session-end.py +26 -0
  8. package/Claude/hooks/session-start.py +35 -0
  9. package/Claude/hooks/stop-extract.py +40 -0
  10. package/Claude/rules/frontmatter.md +54 -0
  11. package/Claude/rules/markdown.md +43 -0
  12. package/Claude/rules/note-organization.md +33 -0
  13. package/Claude/settings.json +54 -0
  14. package/Claude/skills/README.md +136 -0
  15. package/Claude/skills/_lib/__init__.py +1 -0
  16. package/Claude/skills/_lib/api.py +164 -0
  17. package/Claude/skills/_lib/output.py +95 -0
  18. package/Claude/skills/environment/SKILL.md +73 -0
  19. package/Claude/skills/environment/environment.py +239 -0
  20. package/Claude/skills/memory/SKILL.md +153 -0
  21. package/Claude/skills/memory/memory.py +270 -0
  22. package/Claude/skills/project/SKILL.md +105 -0
  23. package/Claude/skills/project/project.py +203 -0
  24. package/Claude/skills/skill-creator/SKILL.md +261 -0
  25. package/Claude/skills/task/SKILL.md +135 -0
  26. package/Claude/skills/task/task.py +310 -0
  27. package/LICENSE +21 -0
  28. package/README.md +176 -0
  29. package/app/app.config.ts +8 -0
  30. package/app/app.vue +39 -0
  31. package/app/assets/css/main.css +10 -0
  32. package/app/components/AppLogo.vue +40 -0
  33. package/app/components/AssistantPanel.client.vue +518 -0
  34. package/app/components/ConfirmModal.vue +84 -0
  35. package/app/components/TemplateMenu.vue +49 -0
  36. package/app/components/agents/AgentActivityChart.client.vue +105 -0
  37. package/app/components/agents/AgentActivityChart.server.vue +25 -0
  38. package/app/components/agents/AgentForm.vue +304 -0
  39. package/app/components/agents/AgentRunModal.vue +154 -0
  40. package/app/components/agents/AgentStatsCards.vue +98 -0
  41. package/app/components/chat/ChatInput.vue +85 -0
  42. package/app/components/chat/ConversationList.vue +78 -0
  43. package/app/components/chat/MessageBubble.vue +81 -0
  44. package/app/components/chat/StreamingMessage.vue +36 -0
  45. package/app/components/chat/ToolCallBlock.vue +77 -0
  46. package/app/components/editor/CodeEditor.client.vue +212 -0
  47. package/app/components/editor/CodeEditorFallback.vue +12 -0
  48. package/app/components/editor/DocumentEditor.vue +326 -0
  49. package/app/components/editor/DocumentMetadata.vue +140 -0
  50. package/app/components/editor/MarkdownEditor.vue +146 -0
  51. package/app/components/files/FileTree.vue +436 -0
  52. package/app/components/hooks/HookActivityChart.client.vue +117 -0
  53. package/app/components/hooks/HookActivityChart.server.vue +25 -0
  54. package/app/components/hooks/HookStatsCards.vue +63 -0
  55. package/app/components/hooks/RecentEventsTable.vue +123 -0
  56. package/app/components/hooks/ToolBreakdownTable.vue +72 -0
  57. package/app/components/search/DashboardSearch.vue +122 -0
  58. package/app/components/tasks/ProjectSelect.vue +35 -0
  59. package/app/components/tasks/TaskCard.vue +182 -0
  60. package/app/components/tasks/TaskDetail.vue +160 -0
  61. package/app/components/tasks/TaskForm.vue +280 -0
  62. package/app/components/tasks/TaskList.vue +69 -0
  63. package/app/components/view/ViewToc.vue +85 -0
  64. package/app/composables/useAgents.ts +153 -0
  65. package/app/composables/useAuth.ts +73 -0
  66. package/app/composables/useChat.ts +298 -0
  67. package/app/composables/useDocument.ts +141 -0
  68. package/app/composables/useEditor.ts +100 -0
  69. package/app/composables/useFileTree.ts +220 -0
  70. package/app/composables/useHookEvents.ts +68 -0
  71. package/app/composables/useMemories.ts +83 -0
  72. package/app/composables/useNotificationBus.ts +154 -0
  73. package/app/composables/usePreferences.ts +131 -0
  74. package/app/composables/useProjects.ts +97 -0
  75. package/app/composables/useSearch.ts +52 -0
  76. package/app/composables/useTasks.ts +201 -0
  77. package/app/composables/useTerminal.ts +135 -0
  78. package/app/layouts/auth.vue +20 -0
  79. package/app/layouts/dashboard.vue +186 -0
  80. package/app/layouts/view.vue +60 -0
  81. package/app/middleware/auth.ts +9 -0
  82. package/app/pages/agents/[id].vue +602 -0
  83. package/app/pages/agents/index.vue +412 -0
  84. package/app/pages/chat.vue +146 -0
  85. package/app/pages/dashboard.vue +80 -0
  86. package/app/pages/docs.vue +131 -0
  87. package/app/pages/hooks.vue +163 -0
  88. package/app/pages/index.vue +249 -0
  89. package/app/pages/login.vue +60 -0
  90. package/app/pages/memories.vue +282 -0
  91. package/app/pages/settings.vue +625 -0
  92. package/app/pages/tasks.vue +312 -0
  93. package/app/pages/view/[uuid].vue +376 -0
  94. package/dist/cli/index.js +2711 -0
  95. package/drizzle.config.ts +10 -0
  96. package/nuxt.config.ts +98 -0
  97. package/package.json +107 -0
  98. package/server/api/agents/[id]/cancel.post.ts +27 -0
  99. package/server/api/agents/[id]/run.post.ts +34 -0
  100. package/server/api/agents/[id]/runs.get.ts +45 -0
  101. package/server/api/agents/[id]/stats.get.ts +94 -0
  102. package/server/api/agents/[id].delete.ts +29 -0
  103. package/server/api/agents/[id].get.ts +25 -0
  104. package/server/api/agents/[id].patch.ts +55 -0
  105. package/server/api/agents/index.get.ts +15 -0
  106. package/server/api/agents/index.post.ts +48 -0
  107. package/server/api/agents/stats.get.ts +86 -0
  108. package/server/api/auth/[...all].ts +5 -0
  109. package/server/api/conversations/[id].delete.ts +16 -0
  110. package/server/api/conversations/[id].get.ts +34 -0
  111. package/server/api/conversations/index.get.ts +17 -0
  112. package/server/api/documents/[id]/index.delete.ts +47 -0
  113. package/server/api/documents/[id]/index.put.ts +102 -0
  114. package/server/api/documents/[id]/public.get.ts +60 -0
  115. package/server/api/documents/[id]/restore.post.ts +65 -0
  116. package/server/api/documents/by-path.post.ts +168 -0
  117. package/server/api/documents/index.get.ts +48 -0
  118. package/server/api/fs/delete.post.ts +41 -0
  119. package/server/api/fs/list.get.ts +99 -0
  120. package/server/api/fs/mkdir.post.ts +44 -0
  121. package/server/api/fs/move.post.ts +68 -0
  122. package/server/api/fs/read.post.ts +48 -0
  123. package/server/api/fs/rename.post.ts +55 -0
  124. package/server/api/fs/write.post.ts +51 -0
  125. package/server/api/health.get.ts +40 -0
  126. package/server/api/home.get.ts +26 -0
  127. package/server/api/hooks/events/index.get.ts +56 -0
  128. package/server/api/hooks/events/index.post.ts +36 -0
  129. package/server/api/hooks/stats.get.ts +99 -0
  130. package/server/api/memory/[id].delete.ts +26 -0
  131. package/server/api/memory/context.get.ts +83 -0
  132. package/server/api/memory/extract.post.ts +42 -0
  133. package/server/api/memory/search.get.ts +70 -0
  134. package/server/api/memory/store.post.ts +31 -0
  135. package/server/api/projects/[id]/index.delete.ts +40 -0
  136. package/server/api/projects/[id]/index.get.ts +25 -0
  137. package/server/api/projects/[id]/index.put.ts +50 -0
  138. package/server/api/projects/index.get.ts +20 -0
  139. package/server/api/projects/index.post.ts +34 -0
  140. package/server/api/secrets/[key].delete.ts +31 -0
  141. package/server/api/secrets/[key].get.ts +30 -0
  142. package/server/api/secrets/[key].put.ts +52 -0
  143. package/server/api/secrets/index.get.ts +20 -0
  144. package/server/api/secrets/index.post.ts +58 -0
  145. package/server/api/tasks/[id]/index.delete.ts +46 -0
  146. package/server/api/tasks/[id]/index.get.ts +24 -0
  147. package/server/api/tasks/[id]/index.put.ts +70 -0
  148. package/server/api/tasks/[id]/restore.post.ts +49 -0
  149. package/server/api/tasks/index.get.ts +53 -0
  150. package/server/api/tasks/index.post.ts +47 -0
  151. package/server/api/tasks/tags.get.ts +21 -0
  152. package/server/api/user/email.patch.ts +56 -0
  153. package/server/db/index.ts +76 -0
  154. package/server/db/migrate.ts +41 -0
  155. package/server/db/schema.ts +345 -0
  156. package/server/db/seed.ts +46 -0
  157. package/server/db/types.ts +28 -0
  158. package/server/drizzle/migrations/0000_brown_george_stacy.sql +34 -0
  159. package/server/drizzle/migrations/0001_stormy_pyro.sql +16 -0
  160. package/server/drizzle/migrations/0002_clean_colossus.sql +50 -0
  161. package/server/drizzle/migrations/0003_fine_joystick.sql +12 -0
  162. package/server/drizzle/migrations/0004_tan_groot.sql +26 -0
  163. package/server/drizzle/migrations/0005_cloudy_lilith.sql +33 -0
  164. package/server/drizzle/migrations/0006_ordinary_retro_girl.sql +13 -0
  165. package/server/drizzle/migrations/0007_flowery_venus.sql +15 -0
  166. package/server/drizzle/migrations/0008_talented_zombie.sql +13 -0
  167. package/server/drizzle/migrations/0009_gray_shen.sql +15 -0
  168. package/server/drizzle/migrations/meta/0000_snapshot.json +230 -0
  169. package/server/drizzle/migrations/meta/0001_snapshot.json +306 -0
  170. package/server/drizzle/migrations/meta/0002_snapshot.json +615 -0
  171. package/server/drizzle/migrations/meta/0003_snapshot.json +730 -0
  172. package/server/drizzle/migrations/meta/0004_snapshot.json +916 -0
  173. package/server/drizzle/migrations/meta/0005_snapshot.json +1127 -0
  174. package/server/drizzle/migrations/meta/0006_snapshot.json +1213 -0
  175. package/server/drizzle/migrations/meta/0007_snapshot.json +1307 -0
  176. package/server/drizzle/migrations/meta/0008_snapshot.json +1390 -0
  177. package/server/drizzle/migrations/meta/0009_snapshot.json +1487 -0
  178. package/server/drizzle/migrations/meta/_journal.json +76 -0
  179. package/server/middleware/auth.ts +79 -0
  180. package/server/plugins/00.env-validate.ts +38 -0
  181. package/server/plugins/01.api-token.ts +31 -0
  182. package/server/plugins/02.database.ts +54 -0
  183. package/server/plugins/03.file-watcher.ts +65 -0
  184. package/server/plugins/04.cron-agents.ts +26 -0
  185. package/server/routes/_ws/chat.ts +252 -0
  186. package/server/routes/notifications.ts +47 -0
  187. package/server/routes/terminal.ts +98 -0
  188. package/server/services/agent-executor.ts +218 -0
  189. package/server/services/cron-scheduler.ts +78 -0
  190. package/server/services/memory-extractor.ts +120 -0
  191. package/server/utils/agent-cleanup.ts +91 -0
  192. package/server/utils/agent-registry.ts +95 -0
  193. package/server/utils/auth.ts +33 -0
  194. package/server/utils/chat-session-manager.ts +59 -0
  195. package/server/utils/crypto.ts +40 -0
  196. package/server/utils/db-guard.ts +12 -0
  197. package/server/utils/db-state.ts +63 -0
  198. package/server/utils/document-sync.ts +207 -0
  199. package/server/utils/frontmatter.ts +84 -0
  200. package/server/utils/notification-bus.ts +60 -0
  201. package/server/utils/path-validator.ts +55 -0
  202. package/server/utils/pty-manager.ts +130 -0
  203. package/shared/types/index.ts +604 -0
  204. package/shared/utils/language-detection.ts +87 -0
  205. package/tsconfig.json +10 -0
@@ -0,0 +1,518 @@
1
+ <script setup lang="ts">
2
+ import { Terminal } from '@xterm/xterm'
3
+ import { FitAddon } from '@xterm/addon-fit'
4
+ import { WebLinksAddon } from '@xterm/addon-web-links'
5
+ import '@xterm/xterm/css/xterm.css'
6
+
7
+ // --- Route ---
8
+ const route = useRoute()
9
+ const isChatPage = computed(() => route.path === '/chat')
10
+
11
+ // --- Preferences ---
12
+ const {
13
+ assistantPanelOpen,
14
+ assistantPanelTab,
15
+ assistantLastConversationId
16
+ } = usePreferences()
17
+
18
+ const isOpen = ref(assistantPanelOpen.value)
19
+ watch(isOpen, (open) => {
20
+ assistantPanelOpen.value = open
21
+ })
22
+
23
+ // --- Tabs ---
24
+ const activeTab = ref(assistantPanelTab.value)
25
+ watch(activeTab, (tab) => {
26
+ assistantPanelTab.value = tab
27
+ })
28
+
29
+ const tabItems = [
30
+ { label: 'Chat', icon: 'i-lucide-message-square', value: 'chat' },
31
+ { label: 'Terminal', icon: 'i-lucide-terminal', value: 'terminal' }
32
+ ]
33
+
34
+ // --- Chat ---
35
+ const {
36
+ connectionStatus: chatConnectionStatus,
37
+ sessionStatus,
38
+ activeConversationId,
39
+ messages,
40
+ conversations,
41
+ streamingText,
42
+ streamingToolCalls,
43
+ loading: chatLoading,
44
+ connect: connectChat,
45
+ sendMessage,
46
+ interrupt,
47
+ startNewConversation,
48
+ loadConversation,
49
+ loadConversations
50
+ } = useChat()
51
+
52
+ const messagesContainer = ref<HTMLElement | null>(null)
53
+
54
+ const conversationItems = computed(() =>
55
+ conversations.value.map(c => ({
56
+ label: c.title || `Chat ${new Date(c.startedAt).toLocaleDateString()}`,
57
+ value: c.id
58
+ }))
59
+ )
60
+
61
+ const selectedConversationId = ref<string | undefined>(
62
+ assistantLastConversationId.value ?? undefined
63
+ )
64
+
65
+ // Sync selection to preference cookie, only load if user picked a different conversation
66
+ watch(selectedConversationId, (id) => {
67
+ assistantLastConversationId.value = id ?? null
68
+ if (id && id !== activeConversationId.value)
69
+ loadConversation(id)
70
+ })
71
+
72
+ // Sync when a new conversation is created via chat:session_created
73
+ watch(activeConversationId, (id) => {
74
+ if (id) selectedConversationId.value = id
75
+ })
76
+
77
+ function handleSend(message: string) {
78
+ sendMessage(message)
79
+ nextTick(scrollToBottom)
80
+ }
81
+
82
+ function handleNewChat() {
83
+ startNewConversation()
84
+ selectedConversationId.value = undefined
85
+ }
86
+
87
+ function scrollToBottom() {
88
+ if (messagesContainer.value)
89
+ messagesContainer.value.scrollTop = messagesContainer.value.scrollHeight
90
+ }
91
+
92
+ watch(streamingText, () => nextTick(scrollToBottom))
93
+ watch(() => messages.value.length, () => nextTick(scrollToBottom))
94
+
95
+ function setupChat() {
96
+ connectChat()
97
+ loadConversations()
98
+ if (assistantLastConversationId.value && !activeConversationId.value)
99
+ loadConversation(assistantLastConversationId.value)
100
+ }
101
+
102
+ // --- Terminal ---
103
+ const terminalRef = ref<HTMLDivElement | null>(null)
104
+ const terminal = ref<Terminal | null>(null)
105
+ const fitAddon = ref<FitAddon | null>(null)
106
+ const terminalInitialized = ref(false)
107
+
108
+ const {
109
+ status: terminalStatus,
110
+ connect: connectTerminal,
111
+ disconnect: disconnectTerminal,
112
+ sendInput,
113
+ sendResize,
114
+ startPingInterval,
115
+ stopPingInterval
116
+ } = useTerminal()
117
+
118
+ const terminalStatusIcon = computed(() => {
119
+ switch (terminalStatus.value) {
120
+ case 'connected': return 'i-lucide-wifi'
121
+ case 'connecting': return 'i-lucide-loader-2'
122
+ default: return 'i-lucide-wifi-off'
123
+ }
124
+ })
125
+
126
+ const chatStatusIcon = computed(() => {
127
+ switch (chatConnectionStatus.value) {
128
+ case 'connected': return 'i-lucide-wifi'
129
+ case 'connecting': return 'i-lucide-loader-2'
130
+ default: return 'i-lucide-wifi-off'
131
+ }
132
+ })
133
+
134
+ function statusClasses(status: string) {
135
+ return [
136
+ status === 'connecting' && 'animate-spin',
137
+ status === 'connected' && 'text-success',
138
+ (status === 'error' || status === 'disconnected') && 'text-muted'
139
+ ]
140
+ }
141
+
142
+ function initTerminal() {
143
+ if (!terminalRef.value || terminalInitialized.value) return
144
+
145
+ const term = new Terminal({
146
+ cursorBlink: true,
147
+ fontSize: 14,
148
+ fontFamily: 'ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace',
149
+ theme: {
150
+ background: '#1a1a1a',
151
+ foreground: '#e5e5e5',
152
+ cursor: '#e5e5e5',
153
+ cursorAccent: '#1a1a1a',
154
+ selectionBackground: '#3b3b3b',
155
+ black: '#1a1a1a',
156
+ red: '#ff5555',
157
+ green: '#50fa7b',
158
+ yellow: '#f1fa8c',
159
+ blue: '#6272a4',
160
+ magenta: '#ff79c6',
161
+ cyan: '#8be9fd',
162
+ white: '#e5e5e5',
163
+ brightBlack: '#4d4d4d',
164
+ brightRed: '#ff6e6e',
165
+ brightGreen: '#69ff94',
166
+ brightYellow: '#ffffa5',
167
+ brightBlue: '#d6acff',
168
+ brightMagenta: '#ff92df',
169
+ brightCyan: '#a4ffff',
170
+ brightWhite: '#ffffff'
171
+ }
172
+ })
173
+
174
+ const fit = new FitAddon()
175
+ fitAddon.value = fit
176
+
177
+ term.loadAddon(fit)
178
+ term.loadAddon(new WebLinksAddon())
179
+
180
+ term.open(terminalRef.value)
181
+ fit.fit()
182
+
183
+ terminal.value = term
184
+ terminalInitialized.value = true
185
+
186
+ term.onData((data) => {
187
+ sendInput(data)
188
+ })
189
+ term.onResize(({ cols, rows }) => {
190
+ sendResize(cols, rows)
191
+ })
192
+
193
+ connectTerminal(term)
194
+ startPingInterval()
195
+ }
196
+
197
+ function handleTerminalResize() {
198
+ if (fitAddon.value && terminal.value)
199
+ fitAddon.value.fit()
200
+ }
201
+
202
+ function handleTerminalReconnect() {
203
+ if (terminalStatus.value === 'connecting') return
204
+ if (!terminalInitialized.value) {
205
+ nextTick(initTerminal)
206
+ return
207
+ }
208
+ if (terminal.value)
209
+ connectTerminal(terminal.value)
210
+ }
211
+
212
+ let resizeObserver: ResizeObserver | null = null
213
+
214
+ function setupTerminal() {
215
+ if (!terminalInitialized.value) {
216
+ initTerminal()
217
+ if (terminalRef.value) {
218
+ resizeObserver = new ResizeObserver(() => handleTerminalResize())
219
+ resizeObserver.observe(terminalRef.value)
220
+ }
221
+ } else {
222
+ handleTerminalResize()
223
+ terminal.value?.focus()
224
+ }
225
+ }
226
+
227
+ // --- Lifecycle ---
228
+ watch(isOpen, (open) => {
229
+ if (open) {
230
+ if (activeTab.value === 'chat') setupChat()
231
+ else nextTick(setupTerminal)
232
+ }
233
+ })
234
+
235
+ watch(activeTab, (tab) => {
236
+ if (!isOpen.value) return
237
+ if (tab === 'chat') setupChat()
238
+ else nextTick(setupTerminal)
239
+ })
240
+
241
+ onMounted(() => {
242
+ if (isOpen.value) {
243
+ if (activeTab.value === 'chat') setupChat()
244
+ else nextTick(setupTerminal)
245
+ }
246
+ })
247
+
248
+ onUnmounted(() => {
249
+ stopPingInterval()
250
+ disconnectTerminal()
251
+ if (resizeObserver) resizeObserver.disconnect()
252
+ if (terminal.value) terminal.value.dispose()
253
+ })
254
+ </script>
255
+
256
+ <template>
257
+ <div
258
+ v-if="!isChatPage"
259
+ class="fixed bottom-4 right-4 z-50"
260
+ >
261
+ <UButton
262
+ :icon="isOpen ? 'i-lucide-x' : 'i-lucide-message-square'"
263
+ size="lg"
264
+ color="primary"
265
+ class="rounded-full!"
266
+ @click="isOpen = !isOpen"
267
+ />
268
+
269
+ <Transition
270
+ enter-active-class="transition-all duration-200 ease-out"
271
+ enter-from-class="opacity-0 translate-y-2 scale-95"
272
+ enter-to-class="opacity-100 translate-y-0 scale-100"
273
+ leave-active-class="transition-all duration-150 ease-in"
274
+ leave-from-class="opacity-100 translate-y-0 scale-100"
275
+ leave-to-class="opacity-0 translate-y-2 scale-95"
276
+ >
277
+ <div
278
+ v-show="isOpen"
279
+ class="assistant-panel absolute bottom-14 right-0 flex flex-col bg-default border-2 rounded-lg overflow-hidden shadow-lg border-default resize"
280
+ >
281
+ <!-- Header: Tabs + controls -->
282
+ <div class="flex items-center justify-between border-b border-default bg-muted/50">
283
+ <UTabs
284
+ v-model="activeTab"
285
+ :items="tabItems"
286
+ variant="link"
287
+ size="sm"
288
+ :content="false"
289
+ class="flex-1"
290
+ />
291
+ <div class="flex items-center gap-1 px-2">
292
+ <UIcon
293
+ :name="activeTab === 'chat' ? chatStatusIcon : terminalStatusIcon"
294
+ :class="[
295
+ 'size-3.5',
296
+ ...statusClasses(activeTab === 'chat' ? chatConnectionStatus : terminalStatus)
297
+ ]"
298
+ />
299
+ <UButton
300
+ size="xs"
301
+ color="neutral"
302
+ variant="ghost"
303
+ icon="i-lucide-x"
304
+ @click="isOpen = false"
305
+ />
306
+ </div>
307
+ </div>
308
+
309
+ <!-- Chat tab -->
310
+ <div
311
+ v-show="activeTab === 'chat'"
312
+ class="flex flex-col flex-1 min-h-0"
313
+ >
314
+ <!-- Conversation selector -->
315
+ <div class="flex items-center gap-2 px-3 py-2 border-b border-default">
316
+ <USelectMenu
317
+ v-model="selectedConversationId"
318
+ :items="conversationItems"
319
+ value-key="value"
320
+ placeholder="New conversation"
321
+ size="xs"
322
+ class="flex-1"
323
+ icon="i-lucide-messages-square"
324
+ />
325
+ <UButton
326
+ icon="i-lucide-plus"
327
+ size="xs"
328
+ color="primary"
329
+ variant="soft"
330
+ @click="handleNewChat"
331
+ />
332
+ </div>
333
+
334
+ <!-- Messages -->
335
+ <div
336
+ ref="messagesContainer"
337
+ class="relative flex-1 overflow-y-auto p-3 space-y-3"
338
+ >
339
+ <!-- Loading overlay -->
340
+ <div
341
+ v-if="chatLoading"
342
+ class="absolute inset-0 flex items-center justify-center bg-elevated/80 z-10"
343
+ >
344
+ <UIcon
345
+ name="i-lucide-loader-2"
346
+ class="size-6 animate-spin text-primary"
347
+ />
348
+ </div>
349
+
350
+ <div
351
+ v-if="messages.length === 0 && sessionStatus !== 'streaming' && !chatLoading"
352
+ class="flex flex-col items-center justify-center h-full text-dimmed"
353
+ >
354
+ <UIcon
355
+ name="i-lucide-message-square"
356
+ class="size-8 mb-2"
357
+ />
358
+ <p class="text-sm">
359
+ Send a message to start chatting
360
+ </p>
361
+ </div>
362
+
363
+ <ChatMessageBubble
364
+ v-for="msg in messages"
365
+ :key="msg.id"
366
+ :message="msg"
367
+ />
368
+
369
+ <ChatStreamingMessage
370
+ v-if="sessionStatus === 'streaming'"
371
+ :text="streamingText"
372
+ :tool-calls="streamingToolCalls"
373
+ />
374
+ </div>
375
+
376
+ <!-- Input -->
377
+ <ChatInput
378
+ :session-status="sessionStatus"
379
+ :connection-status="chatConnectionStatus"
380
+ @send="handleSend"
381
+ @interrupt="interrupt"
382
+ />
383
+ </div>
384
+
385
+ <!-- Terminal tab -->
386
+ <div
387
+ v-show="activeTab === 'terminal'"
388
+ class="flex flex-col flex-1 min-h-0"
389
+ >
390
+ <div class="flex items-center justify-between px-3 py-1.5 border-b border-default text-sm bg-muted/50">
391
+ <div class="flex items-center gap-2 text-muted text-xs">
392
+ <UIcon
393
+ :name="terminalStatusIcon"
394
+ :class="['size-3.5', ...statusClasses(terminalStatus)]"
395
+ />
396
+ <span>{{ terminalStatus === 'connected' ? 'Connected' : terminalStatus === 'connecting' ? 'Connecting...' : terminalStatus === 'error' ? 'Connection error' : 'Disconnected' }}</span>
397
+ </div>
398
+ <UButton
399
+ v-if="terminalStatus === 'disconnected' || terminalStatus === 'error'"
400
+ size="xs"
401
+ color="neutral"
402
+ variant="ghost"
403
+ icon="i-lucide-refresh-cw"
404
+ @click="handleTerminalReconnect"
405
+ >
406
+ Reconnect
407
+ </UButton>
408
+ </div>
409
+
410
+ <div
411
+ ref="terminalRef"
412
+ class="flex-1 p-1 bg-[#1a1a1a]"
413
+ />
414
+ </div>
415
+ </div>
416
+ </Transition>
417
+ </div>
418
+ </template>
419
+
420
+ <style>
421
+ .assistant-panel {
422
+ width: clamp(360px, 45vw, 800px);
423
+ height: clamp(300px, 65vh, 700px);
424
+ min-width: 360px;
425
+ min-height: 300px;
426
+ max-width: 90vw;
427
+ max-height: 80vh;
428
+ }
429
+
430
+ .xterm {
431
+ height: 100%;
432
+ }
433
+
434
+ .xterm-viewport {
435
+ overflow-y: auto !important;
436
+ }
437
+
438
+ /* Compact prose for chat bubbles */
439
+ .chat-prose {
440
+ font-size: 0.8125rem;
441
+ line-height: 1.5;
442
+ }
443
+
444
+ .chat-prose :first-child {
445
+ margin-top: 0;
446
+ }
447
+
448
+ .chat-prose :last-child {
449
+ margin-bottom: 0;
450
+ }
451
+
452
+ .chat-prose p {
453
+ margin-top: 0.375em;
454
+ margin-bottom: 0.375em;
455
+ font-size: 0.9rem;
456
+ line-height: 1.2;
457
+ }
458
+
459
+ .chat-prose h1,
460
+ .chat-prose h2,
461
+ .chat-prose h3,
462
+ .chat-prose h4 {
463
+ margin-top: 0.6em;
464
+ margin-bottom: 0.25em;
465
+ font-size: 1.1rem;
466
+ line-height: 1.4;
467
+ }
468
+
469
+ .chat-prose h1 {
470
+ font-size: 0.9375rem;
471
+ }
472
+
473
+ .chat-prose ul,
474
+ .chat-prose ol {
475
+ margin-top: 0.25em;
476
+ margin-bottom: 0.25em;
477
+ padding-left: 1.25em;
478
+ }
479
+
480
+ .chat-prose li {
481
+ margin-top: 0.11em;
482
+ margin-bottom: 0.11em;
483
+ }
484
+
485
+ .chat-prose pre {
486
+ margin-top: 0.375em;
487
+ margin-bottom: 0.375em;
488
+ padding: 0.5em 0.75em;
489
+ font-size: 0.75rem;
490
+ line-height: 1.5;
491
+ border-radius: 0.375rem;
492
+ }
493
+
494
+ .chat-prose code {
495
+ font-size: 0.75rem;
496
+ }
497
+
498
+ .chat-prose blockquote {
499
+ margin-top: 0.375em;
500
+ margin-bottom: 0.375em;
501
+ padding-left: 0.75em;
502
+ }
503
+
504
+ .chat-prose hr {
505
+ margin-top: 0.5em;
506
+ margin-bottom: 0.5em;
507
+ }
508
+
509
+ .chat-prose table {
510
+ font-size: 0.75rem;
511
+ margin-top: 0.375em;
512
+ margin-bottom: 0.375em;
513
+ }
514
+
515
+ .chat-prose td {
516
+ padding: 0.4em;
517
+ }
518
+ </style>
@@ -0,0 +1,84 @@
1
+ <script setup lang="ts">
2
+ withDefaults(defineProps<{
3
+ open: boolean
4
+ title?: string
5
+ description?: string
6
+ confirmLabel?: string
7
+ cancelLabel?: string
8
+ confirmColor?: 'primary' | 'error' | 'warning' | 'success' | 'info' | 'neutral'
9
+ icon?: string
10
+ loading?: boolean
11
+ }>(), {
12
+ title: 'Confirm Action',
13
+ description: 'Are you sure you want to proceed?',
14
+ confirmLabel: 'Confirm',
15
+ cancelLabel: 'Cancel',
16
+ confirmColor: 'primary',
17
+ icon: undefined,
18
+ loading: false
19
+ })
20
+
21
+ const emit = defineEmits<{
22
+ 'update:open': [value: boolean]
23
+ 'confirm': []
24
+ 'cancel': []
25
+ }>()
26
+
27
+ function close() {
28
+ emit('update:open', false)
29
+ emit('cancel')
30
+ }
31
+
32
+ function confirm() {
33
+ emit('confirm')
34
+ }
35
+ </script>
36
+
37
+ <template>
38
+ <UModal
39
+ :open="open"
40
+ @update:open="emit('update:open', $event)"
41
+ >
42
+ <template #content>
43
+ <div class="p-6">
44
+ <div class="flex items-start gap-4">
45
+ <div
46
+ v-if="icon"
47
+ class="shrink-0 p-2 rounded-full bg-error/10"
48
+ >
49
+ <UIcon
50
+ :name="icon"
51
+ class="size-6 text-error"
52
+ />
53
+ </div>
54
+ <div class="flex-1 min-w-0">
55
+ <h3 class="text-lg font-semibold text-default">
56
+ {{ title }}
57
+ </h3>
58
+ <p class="mt-2 text-sm text-muted">
59
+ {{ description }}
60
+ </p>
61
+ </div>
62
+ </div>
63
+
64
+ <div class="mt-6 flex justify-end gap-3">
65
+ <UButton
66
+ color="neutral"
67
+ variant="ghost"
68
+ :disabled="loading"
69
+ @click="close"
70
+ >
71
+ {{ cancelLabel }}
72
+ </UButton>
73
+ <UButton
74
+ :color="confirmColor"
75
+ :loading="loading"
76
+ @click="confirm"
77
+ >
78
+ {{ confirmLabel }}
79
+ </UButton>
80
+ </div>
81
+ </div>
82
+ </template>
83
+ </UModal>
84
+ </template>
@@ -0,0 +1,49 @@
1
+ <template>
2
+ <UDropdownMenu
3
+ v-slot="{ open }"
4
+ :modal="false"
5
+ :items="[{
6
+ label: 'Starter',
7
+ to: 'https://starter-template.nuxt.dev/',
8
+ color: 'primary',
9
+ checked: true,
10
+ type: 'checkbox'
11
+ }, {
12
+ label: 'Landing',
13
+ to: 'https://landing-template.nuxt.dev/'
14
+ }, {
15
+ label: 'Docs',
16
+ to: 'https://docs-template.nuxt.dev/'
17
+ }, {
18
+ label: 'SaaS',
19
+ to: 'https://saas-template.nuxt.dev/'
20
+ }, {
21
+ label: 'Dashboard',
22
+ to: 'https://dashboard-template.nuxt.dev/'
23
+ }, {
24
+ label: 'Chat',
25
+ to: 'https://chat-template.nuxt.dev/'
26
+ }, {
27
+ label: 'Portfolio',
28
+ to: 'https://portfolio-template.nuxt.dev/'
29
+ }, {
30
+ label: 'Changelog',
31
+ to: 'https://changelog-template.nuxt.dev/'
32
+ }]"
33
+ :content="{ align: 'start' }"
34
+ :ui="{ content: 'min-w-fit' }"
35
+ size="xs"
36
+ >
37
+ <UButton
38
+ label="Starter"
39
+ variant="subtle"
40
+ trailing-icon="i-lucide-chevron-down"
41
+ size="xs"
42
+ class="-mb-[6px] font-semibold rounded-full truncate"
43
+ :class="[open && 'bg-primary/15']"
44
+ :ui="{
45
+ trailingIcon: ['transition-transform duration-200', open ? 'rotate-180' : undefined].filter(Boolean).join(' ')
46
+ }"
47
+ />
48
+ </UDropdownMenu>
49
+ </template>