@ailog/cli 0.1.4 → 0.2.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 (108) hide show
  1. package/dist/bin/cli.js +4 -3
  2. package/dist/standalone/.next/BUILD_ID +1 -1
  3. package/dist/standalone/.next/build-manifest.json +2 -2
  4. package/dist/standalone/.next/prerender-manifest.json +3 -3
  5. package/dist/standalone/.next/server/app/_global-error.html +2 -2
  6. package/dist/standalone/.next/server/app/_global-error.rsc +1 -1
  7. package/dist/standalone/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  8. package/dist/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  9. package/dist/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  10. package/dist/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  11. package/dist/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  12. package/dist/standalone/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  13. package/dist/standalone/.next/server/app/_not-found.html +1 -1
  14. package/dist/standalone/.next/server/app/_not-found.rsc +7 -7
  15. package/dist/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +7 -7
  16. package/dist/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  17. package/dist/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +7 -7
  18. package/dist/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  19. package/dist/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  20. package/dist/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
  21. package/dist/standalone/.next/server/app/api/[[...route]]/route.js.nft.json +1 -1
  22. package/dist/standalone/.next/server/app/apps/[appId]/page.js.nft.json +1 -1
  23. package/dist/standalone/.next/server/app/apps/[appId]/page_client-reference-manifest.js +1 -1
  24. package/dist/standalone/.next/server/app/apps/[appId]/settings/page.js.nft.json +1 -1
  25. package/dist/standalone/.next/server/app/apps/[appId]/settings/page_client-reference-manifest.js +1 -1
  26. package/dist/standalone/.next/server/app/apps/[appId]/threads/[threadId]/page.js.nft.json +1 -1
  27. package/dist/standalone/.next/server/app/apps/[appId]/threads/[threadId]/page_client-reference-manifest.js +1 -1
  28. package/dist/standalone/.next/server/app/index.html +1 -1
  29. package/dist/standalone/.next/server/app/index.rsc +8 -8
  30. package/dist/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
  31. package/dist/standalone/.next/server/app/index.segments/_full.segment.rsc +8 -8
  32. package/dist/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  33. package/dist/standalone/.next/server/app/index.segments/_index.segment.rsc +7 -7
  34. package/dist/standalone/.next/server/app/index.segments/_tree.segment.rsc +2 -2
  35. package/dist/standalone/.next/server/app/page.js.nft.json +1 -1
  36. package/dist/standalone/.next/server/app/page_client-reference-manifest.js +1 -1
  37. package/dist/standalone/.next/server/chunks/ssr/{_d7dc3d79._.js → _289820d0._.js} +2 -2
  38. package/dist/standalone/.next/server/chunks/ssr/_70dfbe56._.js +3 -0
  39. package/dist/standalone/.next/server/chunks/ssr/_8c0bfb07._.js +3 -0
  40. package/dist/standalone/.next/server/chunks/ssr/{_ebd188d6._.js → _e5c457a7._.js} +2 -2
  41. package/dist/standalone/.next/server/chunks/ssr/_fce9eb14._.js +1 -1
  42. package/dist/standalone/.next/server/chunks/ssr/apps_web_app_apps_[appId]_threads_[threadId]_page_tsx_7dd7d764._.js +37 -1
  43. package/dist/standalone/.next/server/chunks/ssr/apps_web_app_page_tsx_66bf978b._.js +1 -1
  44. package/dist/standalone/.next/server/chunks/ssr/apps_web_components_ui_9822aec4._.js +3 -0
  45. package/dist/standalone/.next/server/chunks/ssr/{_877d9b8d._.js → apps_web_components_ui_alert-dialog_tsx_9188904e._.js} +2 -2
  46. package/dist/standalone/.next/server/chunks/ssr/apps_web_components_ui_cbe5b2b9._.js +3 -0
  47. package/dist/standalone/.next/server/chunks/ssr/{apps_web_components_ui_15d0ea42._.js → apps_web_components_ui_cc5b288d._.js} +2 -2
  48. package/dist/standalone/.next/server/chunks/ssr/node_modules__bun_2e4b1ed0._.js +3 -0
  49. package/dist/standalone/.next/server/chunks/ssr/node_modules__bun_d2520564._.js +3 -0
  50. package/dist/standalone/.next/server/pages/404.html +1 -1
  51. package/dist/standalone/.next/server/pages/500.html +2 -2
  52. package/dist/standalone/.next/server/server-reference-manifest.js +1 -1
  53. package/dist/standalone/.next/server/server-reference-manifest.json +1 -1
  54. package/dist/standalone/.next/static/chunks/5521918d58602ba4.css +4 -0
  55. package/dist/standalone/.next/static/chunks/61ce30fe62669a20.js +1 -0
  56. package/dist/standalone/.next/static/chunks/6ee1158d12a0c765.js +1 -0
  57. package/dist/standalone/.next/static/chunks/8bd29684c211a1e1.js +37 -0
  58. package/dist/standalone/.next/static/chunks/8ef339e3fd7a82de.js +1 -0
  59. package/dist/standalone/.next/static/chunks/{f9feac1b69b1185e.js → b337946fdc3c17b3.js} +1 -1
  60. package/dist/standalone/.next/static/chunks/{6b7a6f475b2cb221.js → b46c6c394477214a.js} +1 -1
  61. package/dist/standalone/.next/static/chunks/bbf59b867827b224.js +7 -0
  62. package/dist/standalone/.next/static/chunks/c416de6f3efbbe15.js +7 -0
  63. package/dist/standalone/.next/static/chunks/d70eda393c3966c3.js +1 -0
  64. package/dist/standalone/dist/standalone/dist/standalone/dist/standalone/dist/standalone/dist/standalone/app/apps/[appId]/layout.tsx +3 -0
  65. package/dist/standalone/dist/standalone/dist/standalone/dist/standalone/dist/standalone/dist/standalone/app/apps/[appId]/page.tsx +238 -0
  66. package/dist/standalone/dist/standalone/dist/standalone/dist/standalone/dist/standalone/dist/standalone/app/apps/[appId]/settings/page.tsx +224 -0
  67. package/dist/standalone/dist/standalone/dist/standalone/dist/standalone/dist/standalone/dist/standalone/app/apps/[appId]/threads/[threadId]/page.tsx +283 -0
  68. package/dist/standalone/dist/standalone/dist/standalone/dist/standalone/dist/standalone/dist/standalone/components/apps-table.tsx +274 -0
  69. package/dist/standalone/dist/standalone/dist/standalone/dist/standalone/dist/standalone/dist/standalone/components.json +24 -0
  70. package/dist/standalone/dist/standalone/dist/standalone/dist/standalone/dist/standalone/dist/standalone/dist/standalone/app/apps/[appId]/layout.tsx +3 -0
  71. package/dist/standalone/dist/standalone/dist/standalone/dist/standalone/dist/standalone/dist/standalone/dist/standalone/app/apps/[appId]/page.tsx +238 -0
  72. package/dist/standalone/dist/standalone/dist/standalone/dist/standalone/dist/standalone/dist/standalone/dist/standalone/app/apps/[appId]/settings/page.tsx +224 -0
  73. package/dist/standalone/dist/standalone/dist/standalone/dist/standalone/dist/standalone/dist/standalone/dist/standalone/app/apps/[appId]/threads/[threadId]/page.tsx +283 -0
  74. package/dist/standalone/dist/standalone/dist/standalone/dist/standalone/dist/standalone/dist/standalone/dist/standalone/components/apps-table.tsx +274 -0
  75. package/dist/standalone/dist/standalone/dist/standalone/dist/standalone/dist/standalone/dist/standalone/dist/standalone/components.json +24 -0
  76. package/dist/standalone/dist/standalone/dist/standalone/dist/standalone/dist/standalone/dist/standalone/dist/standalone/dist/standalone/app/apps/[appId]/layout.tsx +3 -0
  77. package/dist/standalone/dist/standalone/dist/standalone/dist/standalone/dist/standalone/dist/standalone/dist/standalone/dist/standalone/app/apps/[appId]/page.tsx +238 -0
  78. package/dist/standalone/dist/standalone/dist/standalone/dist/standalone/dist/standalone/dist/standalone/dist/standalone/dist/standalone/app/apps/[appId]/settings/page.tsx +224 -0
  79. package/dist/standalone/dist/standalone/dist/standalone/dist/standalone/dist/standalone/dist/standalone/dist/standalone/dist/standalone/app/apps/[appId]/threads/[threadId]/page.tsx +283 -0
  80. package/dist/standalone/dist/standalone/dist/standalone/dist/standalone/dist/standalone/dist/standalone/dist/standalone/dist/standalone/components/apps-table.tsx +274 -0
  81. package/dist/standalone/dist/standalone/dist/standalone/dist/standalone/dist/standalone/dist/standalone/dist/standalone/dist/standalone/components.json +24 -0
  82. package/dist/standalone/dist/standalone/dist/standalone/dist/standalone/dist/standalone/dist/standalone/dist/standalone/dist/standalone/package.json +66 -0
  83. package/dist/standalone/dist/standalone/dist/standalone/dist/standalone/dist/standalone/dist/standalone/dist/standalone/dist/standalone/tsconfig.json +34 -0
  84. package/dist/standalone/dist/standalone/dist/standalone/dist/standalone/dist/standalone/dist/standalone/dist/standalone/package.json +65 -0
  85. package/dist/standalone/dist/standalone/dist/standalone/dist/standalone/dist/standalone/dist/standalone/dist/standalone/tsconfig.json +34 -0
  86. package/dist/standalone/dist/standalone/dist/standalone/dist/standalone/dist/standalone/dist/standalone/package.json +65 -0
  87. package/dist/standalone/dist/standalone/dist/standalone/dist/standalone/dist/standalone/dist/standalone/tsconfig.json +34 -0
  88. package/dist/standalone/dist/standalone/dist/standalone/dist/standalone/dist/standalone/package.json +1 -2
  89. package/dist/standalone/dist/standalone/dist/standalone/dist/standalone/package.json +1 -1
  90. package/dist/standalone/dist/standalone/dist/standalone/package.json +4 -3
  91. package/dist/standalone/dist/standalone/package.json +4 -3
  92. package/dist/standalone/package.json +4 -3
  93. package/package.json +4 -3
  94. package/dist/standalone/.next/server/chunks/ssr/_1b351fd8._.js +0 -3
  95. package/dist/standalone/.next/server/chunks/ssr/_82db472c._.js +0 -3
  96. package/dist/standalone/.next/server/chunks/ssr/_ba4460f0._.js +0 -3
  97. package/dist/standalone/.next/server/chunks/ssr/apps_web_components_ui_1626551a._.js +0 -3
  98. package/dist/standalone/.next/static/chunks/0a3222f50a8ba36d.js +0 -1
  99. package/dist/standalone/.next/static/chunks/0d93d2e4091cb1d5.js +0 -1
  100. package/dist/standalone/.next/static/chunks/15389c2c0efe0c58.js +0 -7
  101. package/dist/standalone/.next/static/chunks/46a444f4110b5209.js +0 -7
  102. package/dist/standalone/.next/static/chunks/540b3a38f32c78d0.js +0 -1
  103. package/dist/standalone/.next/static/chunks/5966736c7dbc65d2.js +0 -1
  104. package/dist/standalone/.next/static/chunks/b7ea835c5478b248.js +0 -1
  105. package/dist/standalone/.next/static/chunks/e4876b96721a69ca.css +0 -4
  106. /package/dist/standalone/.next/static/{HzNPAAEgDfSt2GDQklLOp → mh3xsqAwvHCM4LDWJwA0X}/_buildManifest.js +0 -0
  107. /package/dist/standalone/.next/static/{HzNPAAEgDfSt2GDQklLOp → mh3xsqAwvHCM4LDWJwA0X}/_clientMiddlewareManifest.json +0 -0
  108. /package/dist/standalone/.next/static/{HzNPAAEgDfSt2GDQklLOp → mh3xsqAwvHCM4LDWJwA0X}/_ssgManifest.js +0 -0
@@ -0,0 +1,283 @@
1
+ 'use client'
2
+
3
+ import { useCallback, useEffect, useMemo, useRef, useState } from 'react'
4
+ import { useParams } from 'next/navigation'
5
+ import { Card, CardContent, CardHeader } from '@/components/ui/card'
6
+ import { Button } from '@/components/ui/button'
7
+ import { Skeleton } from '@/components/ui/skeleton'
8
+ import { ChatView } from '@/components/chat-view'
9
+ import { ScrollArea } from '@/components/ui/scroll-area'
10
+ import { RequestDetailPanel } from '@/components/request-detail-panel'
11
+ import { GenerationDetailPanel } from '@/components/generation-detail-panel'
12
+ import { GenerationTimeline } from '@/components/generation-timeline'
13
+ import { useKeydown } from '@/hooks/use-keydown'
14
+ import { rpcClient } from '@/lib/rpc-client'
15
+ import type { Thread, Generation } from '@/lib/api'
16
+
17
+ export default function ThreadDetailPage() {
18
+ const params = useParams()
19
+ const appId = params.appId as string
20
+ const threadId = params.threadId as string
21
+
22
+ const [thread, setThread] = useState<Thread | null>(null)
23
+ const [generations, setGenerations] = useState<Generation[]>([])
24
+ const [isLoading, setIsLoading] = useState(true)
25
+ const [selectedRequestId, setSelectedRequestId] = useState<string | null>(
26
+ null,
27
+ )
28
+ const [selectedGenerationId, setSelectedGenerationId] = useState<
29
+ string | null
30
+ >(null)
31
+ const [viewMode, setViewMode] = useState<'details' | 'timeline'>('details')
32
+
33
+ // Keyboard navigation for generations
34
+ const handleKeyDown = useCallback(
35
+ (e: KeyboardEvent) => {
36
+ if (generations.length === 0) return
37
+
38
+ const currentIndex = generations.findIndex(
39
+ (g) => g.generationId === selectedGenerationId,
40
+ )
41
+
42
+ if (e.key === 'ArrowUp') {
43
+ e.preventDefault()
44
+ if (currentIndex === -1) {
45
+ setSelectedGenerationId(generations[0].generationId)
46
+ setSelectedRequestId(null)
47
+ } else if (currentIndex < generations.length - 1) {
48
+ setSelectedGenerationId(generations[currentIndex + 1].generationId)
49
+ setSelectedRequestId(null)
50
+ }
51
+ } else if (e.key === 'ArrowDown') {
52
+ e.preventDefault()
53
+ if (currentIndex > 0) {
54
+ setSelectedGenerationId(generations[currentIndex - 1].generationId)
55
+ setSelectedRequestId(null)
56
+ } else if (currentIndex === -1 && generations.length > 0) {
57
+ setSelectedGenerationId(
58
+ generations[generations.length - 1].generationId,
59
+ )
60
+ setSelectedRequestId(null)
61
+ }
62
+ }
63
+ },
64
+ [generations, selectedGenerationId],
65
+ )
66
+
67
+ useKeydown(handleKeyDown)
68
+
69
+ const leftContainerRef = useRef<HTMLDivElement>(null)
70
+
71
+ // Scroll to selected generation (within ScrollArea only, not main page)
72
+ useEffect(() => {
73
+ if (!selectedGenerationId || !leftContainerRef.current) return
74
+
75
+ const viewport = leftContainerRef.current.querySelector(
76
+ '[data-slot="scroll-area-viewport"]',
77
+ )
78
+ const element = leftContainerRef.current.querySelector(
79
+ `[data-generation-id="${selectedGenerationId}"]`,
80
+ )
81
+
82
+ if (viewport && element) {
83
+ const viewportRect = viewport.getBoundingClientRect()
84
+ const elementRect = element.getBoundingClientRect()
85
+ const scrollTop = viewport.scrollTop
86
+
87
+ const elementTop = elementRect.top - viewportRect.top + scrollTop
88
+ const elementBottom = elementTop + elementRect.height
89
+ const viewportHeight = viewportRect.height
90
+
91
+ if (elementTop < scrollTop) {
92
+ viewport.scrollTo({ top: elementTop, behavior: 'smooth' })
93
+ } else if (elementBottom > scrollTop + viewportHeight) {
94
+ viewport.scrollTo({
95
+ top: elementBottom - viewportHeight,
96
+ behavior: 'smooth',
97
+ })
98
+ }
99
+ }
100
+ }, [selectedGenerationId])
101
+
102
+ useEffect(() => {
103
+ async function fetchData() {
104
+ try {
105
+ // Fetch thread and all generations for this thread
106
+ const [threadRes, generationsRes] = await Promise.all([
107
+ rpcClient.api.admin.apps[':appId'].threads[':threadId'].$get({
108
+ param: { appId, threadId },
109
+ }),
110
+ rpcClient.api.admin.apps[':appId'].generations.$get({
111
+ param: { appId },
112
+ query: { threadId },
113
+ }),
114
+ ])
115
+
116
+ if (threadRes.ok) {
117
+ const threadData = await threadRes.json()
118
+ setThread(threadData as Thread)
119
+ }
120
+
121
+ if (generationsRes.ok) {
122
+ const generationsData = await generationsRes.json()
123
+ setGenerations(generationsData.generations as Generation[])
124
+ }
125
+ } catch (error) {
126
+ console.error('Failed to fetch data:', error)
127
+ } finally {
128
+ setIsLoading(false)
129
+ }
130
+ }
131
+ fetchData()
132
+ }, [appId, threadId])
133
+
134
+ // Derive requests from generations by grouping unique requestIds
135
+ const requests = useMemo(() => {
136
+ const requestMap = new Map<
137
+ string,
138
+ { requestId: string; createdAt: string }
139
+ >()
140
+ for (const gen of generations) {
141
+ if (!requestMap.has(gen.requestId)) {
142
+ requestMap.set(gen.requestId, {
143
+ requestId: gen.requestId,
144
+ createdAt: gen.createdAt,
145
+ })
146
+ } else {
147
+ // Use the earliest createdAt for the request
148
+ const existing = requestMap.get(gen.requestId)!
149
+ if (gen.createdAt < existing.createdAt) {
150
+ existing.createdAt = gen.createdAt
151
+ }
152
+ }
153
+ }
154
+ return Array.from(requestMap.values())
155
+ }, [generations])
156
+
157
+ return (
158
+ <div className="space-y-4">
159
+ {/* Thread Info / Timeline */}
160
+ <Card className="py-2">
161
+ <CardHeader className="px-3 py-2">
162
+ <div className="flex items-center justify-between">
163
+ <div className="flex gap-1">
164
+ <Button
165
+ variant={viewMode === 'details' ? 'secondary' : 'ghost'}
166
+ size="sm"
167
+ className="h-6 px-2 text-xs"
168
+ onClick={() => setViewMode('details')}
169
+ >
170
+ Details
171
+ </Button>
172
+ <Button
173
+ variant={viewMode === 'timeline' ? 'secondary' : 'ghost'}
174
+ size="sm"
175
+ className="h-6 px-2 text-xs"
176
+ onClick={() => setViewMode('timeline')}
177
+ disabled={generations.length === 0}
178
+ >
179
+ Timeline
180
+ </Button>
181
+ </div>
182
+ {isLoading ? (
183
+ <Skeleton className="h-4 w-24" />
184
+ ) : (
185
+ <span className="text-muted-foreground text-xs">
186
+ {thread?.title || '(Untitled)'}
187
+ </span>
188
+ )}
189
+ </div>
190
+ </CardHeader>
191
+ <CardContent className="px-3 pt-0 pb-2">
192
+ {viewMode === 'details' ? (
193
+ isLoading ? (
194
+ <div className="flex gap-6">
195
+ <Skeleton className="h-3 w-24" />
196
+ <Skeleton className="h-3 w-24" />
197
+ </div>
198
+ ) : (
199
+ <div className="flex flex-wrap gap-x-6 gap-y-1 text-xs">
200
+ <div>
201
+ <span className="text-muted-foreground">Thread ID: </span>
202
+ <span className="font-mono">{thread?.threadId}</span>
203
+ </div>
204
+ {thread?.tenantId && (
205
+ <div>
206
+ <span className="text-muted-foreground">Tenant ID: </span>
207
+ <span>{thread.tenantId}</span>
208
+ </div>
209
+ )}
210
+ <div>
211
+ <span className="text-muted-foreground">User: </span>
212
+ <span className="font-mono">{thread?.userId}</span>
213
+ </div>
214
+ </div>
215
+ )
216
+ ) : (
217
+ <GenerationTimeline
218
+ generations={generations}
219
+ selectedGenerationId={selectedGenerationId}
220
+ onSelectGeneration={(generationId) => {
221
+ setSelectedGenerationId(generationId)
222
+ setSelectedRequestId(null)
223
+ }}
224
+ />
225
+ )}
226
+ </CardContent>
227
+ </Card>
228
+
229
+ {/* Split View: Chat + Details */}
230
+ <div className="grid min-w-0 grid-cols-[minmax(0,1fr)_minmax(0,2fr)] gap-3">
231
+ <ScrollArea
232
+ className="h-[calc(100vh-220px)] [&_[data-slot=scroll-area-viewport]>div]:!block"
233
+ ref={leftContainerRef}
234
+ >
235
+ {/* Left: Chat View */}
236
+ <ChatView
237
+ requests={requests}
238
+ generations={generations}
239
+ isLoading={isLoading}
240
+ selectedRequestId={selectedRequestId}
241
+ selectedGenerationId={selectedGenerationId}
242
+ onSelectRequest={(requestId) => {
243
+ setSelectedRequestId(requestId)
244
+ setSelectedGenerationId(null)
245
+ }}
246
+ onSelectGeneration={(generationId) => {
247
+ setSelectedGenerationId(generationId)
248
+ setSelectedRequestId(null)
249
+ }}
250
+ />
251
+ </ScrollArea>
252
+
253
+ <ScrollArea className="h-[calc(100vh-220px)] border [&_[data-slot=scroll-area-viewport]>div]:!block">
254
+ {/* Right: Details Panel */}
255
+ <div className="w-full min-w-0 overflow-hidden p-4">
256
+ {selectedGenerationId ? (
257
+ <GenerationDetailPanel
258
+ generation={
259
+ generations.find(
260
+ (g) => g.generationId === selectedGenerationId,
261
+ ) || null
262
+ }
263
+ />
264
+ ) : (
265
+ <RequestDetailPanel
266
+ request={
267
+ requests.find((r) => r.requestId === selectedRequestId) ||
268
+ null
269
+ }
270
+ generations={generations}
271
+ selectedGenerationId={selectedGenerationId}
272
+ onSelectGeneration={(generationId) => {
273
+ setSelectedGenerationId(generationId)
274
+ setSelectedRequestId(null)
275
+ }}
276
+ />
277
+ )}
278
+ </div>
279
+ </ScrollArea>
280
+ </div>
281
+ </div>
282
+ )
283
+ }
@@ -0,0 +1,274 @@
1
+ 'use client'
2
+
3
+ import { useState } from 'react'
4
+ import { useRouter } from 'next/navigation'
5
+ import Link from 'next/link'
6
+ import {
7
+ Table,
8
+ TableBody,
9
+ TableCell,
10
+ TableHead,
11
+ TableHeader,
12
+ TableRow,
13
+ } from '@/components/ui/table'
14
+ import {
15
+ DropdownMenu,
16
+ DropdownMenuContent,
17
+ DropdownMenuItem,
18
+ DropdownMenuTrigger,
19
+ } from '@/components/ui/dropdown-menu'
20
+ import {
21
+ AlertDialog,
22
+ AlertDialogAction,
23
+ AlertDialogCancel,
24
+ AlertDialogContent,
25
+ AlertDialogDescription,
26
+ AlertDialogFooter,
27
+ AlertDialogHeader,
28
+ AlertDialogTitle,
29
+ } from '@/components/ui/alert-dialog'
30
+ import { Button } from '@/components/ui/button'
31
+ import { Skeleton } from '@/components/ui/skeleton'
32
+ import {
33
+ Tooltip,
34
+ TooltipContent,
35
+ TooltipTrigger,
36
+ } from '@/components/ui/tooltip'
37
+ import { HugeiconsIcon } from '@hugeicons/react'
38
+ import {
39
+ MoreHorizontalCircle01Icon,
40
+ EyeIcon,
41
+ Delete02Icon,
42
+ Copy01Icon,
43
+ } from '@hugeicons/core-free-icons'
44
+ import { rpcClient } from '@/lib/rpc-client'
45
+ import { toast } from 'sonner'
46
+ import type { App } from '@/lib/api'
47
+
48
+ interface AppsTableProps {
49
+ apps: App[]
50
+ isLoading: boolean
51
+ onDelete: () => void
52
+ }
53
+
54
+ function formatDate(dateString: string) {
55
+ return new Date(dateString).toLocaleDateString('en-US', {
56
+ year: 'numeric',
57
+ month: 'short',
58
+ day: 'numeric',
59
+ hour: '2-digit',
60
+ minute: '2-digit',
61
+ })
62
+ }
63
+
64
+ function copyToClipboard(text: string) {
65
+ navigator.clipboard.writeText(text)
66
+ toast.success('Copied to clipboard')
67
+ }
68
+
69
+ function truncateId(id: string, maxLength = 12) {
70
+ return id.length > maxLength ? `${id.substring(0, maxLength)}...` : id
71
+ }
72
+
73
+ export function AppsTable({ apps, isLoading, onDelete }: AppsTableProps) {
74
+ const router = useRouter()
75
+ const [deleteDialogOpen, setDeleteDialogOpen] = useState(false)
76
+ const [appToDelete, setAppToDelete] = useState<App | null>(null)
77
+ const [isDeleting, setIsDeleting] = useState(false)
78
+
79
+ const handleDelete = async () => {
80
+ if (!appToDelete) return
81
+
82
+ setIsDeleting(true)
83
+ try {
84
+ const res = await rpcClient.api.admin.apps[':appId'].$delete({
85
+ param: { appId: appToDelete.appId },
86
+ })
87
+
88
+ if (!res.ok) {
89
+ throw new Error('Failed to delete app')
90
+ }
91
+
92
+ toast.success('App deleted successfully')
93
+ setDeleteDialogOpen(false)
94
+ setAppToDelete(null)
95
+ onDelete()
96
+ } catch (error) {
97
+ toast.error('Failed to delete app')
98
+ console.error(error)
99
+ } finally {
100
+ setIsDeleting(false)
101
+ }
102
+ }
103
+
104
+ if (isLoading) {
105
+ return (
106
+ <div className="rounded-md border">
107
+ <Table>
108
+ <TableHeader>
109
+ <TableRow>
110
+ <TableHead>App ID</TableHead>
111
+ <TableHead>Name</TableHead>
112
+ <TableHead>Created</TableHead>
113
+ <TableHead>Updated</TableHead>
114
+ <TableHead className="w-[70px]"></TableHead>
115
+ </TableRow>
116
+ </TableHeader>
117
+ <TableBody>
118
+ {[...Array(3)].map((_, i) => (
119
+ <TableRow key={i}>
120
+ <TableCell>
121
+ <Skeleton className="h-5 w-48" />
122
+ </TableCell>
123
+ <TableCell>
124
+ <Skeleton className="h-5 w-32" />
125
+ </TableCell>
126
+ <TableCell>
127
+ <Skeleton className="h-5 w-36" />
128
+ </TableCell>
129
+ <TableCell>
130
+ <Skeleton className="h-5 w-36" />
131
+ </TableCell>
132
+ <TableCell>
133
+ <Skeleton className="h-8 w-8" />
134
+ </TableCell>
135
+ </TableRow>
136
+ ))}
137
+ </TableBody>
138
+ </Table>
139
+ </div>
140
+ )
141
+ }
142
+
143
+ if (apps.length === 0) {
144
+ return (
145
+ <div className="rounded-md border p-8 text-center">
146
+ <p className="text-muted-foreground">
147
+ No apps yet. Create your first app to get started.
148
+ </p>
149
+ </div>
150
+ )
151
+ }
152
+
153
+ return (
154
+ <>
155
+ <div className="rounded-md border">
156
+ <Table>
157
+ <TableHeader>
158
+ <TableRow>
159
+ <TableHead>App ID</TableHead>
160
+ <TableHead>Name</TableHead>
161
+ <TableHead>Created</TableHead>
162
+ <TableHead>Updated</TableHead>
163
+ <TableHead className="w-[70px]"></TableHead>
164
+ </TableRow>
165
+ </TableHeader>
166
+ <TableBody>
167
+ {apps.map((app) => (
168
+ <TableRow
169
+ key={app.appId}
170
+ className="cursor-pointer"
171
+ onClick={() => router.push(`/apps/${app.appId}`)}
172
+ >
173
+ <TableCell>
174
+ <div className="flex items-center gap-2">
175
+ {app.appId.length > 12 ? (
176
+ <Tooltip>
177
+ <TooltipTrigger asChild>
178
+ <code className="text-muted-foreground cursor-default font-mono text-sm">
179
+ {truncateId(app.appId)}
180
+ </code>
181
+ </TooltipTrigger>
182
+ <TooltipContent>{app.appId}</TooltipContent>
183
+ </Tooltip>
184
+ ) : (
185
+ <code className="text-muted-foreground font-mono text-sm">
186
+ {app.appId}
187
+ </code>
188
+ )}
189
+ <Button
190
+ variant="ghost"
191
+ size="icon"
192
+ className="h-6 w-6"
193
+ onClick={(e) => {
194
+ e.stopPropagation()
195
+ copyToClipboard(app.appId)
196
+ }}
197
+ >
198
+ <HugeiconsIcon icon={Copy01Icon} className="h-3 w-3" />
199
+ </Button>
200
+ </div>
201
+ </TableCell>
202
+ <TableCell className="font-medium">{app.name}</TableCell>
203
+ <TableCell className="text-muted-foreground">
204
+ {formatDate(app.createdAt)}
205
+ </TableCell>
206
+ <TableCell className="text-muted-foreground">
207
+ {formatDate(app.updatedAt)}
208
+ </TableCell>
209
+ <TableCell className="p-0" onClick={(e) => e.stopPropagation()}>
210
+ <DropdownMenu>
211
+ <DropdownMenuTrigger asChild>
212
+ <button className="hover:bg-muted/50 flex h-full w-full items-center justify-center p-4">
213
+ <HugeiconsIcon
214
+ icon={MoreHorizontalCircle01Icon}
215
+ className="h-4 w-4"
216
+ />
217
+ </button>
218
+ </DropdownMenuTrigger>
219
+ <DropdownMenuContent align="end">
220
+ <DropdownMenuItem asChild>
221
+ <Link href={`/apps/${app.appId}`}>
222
+ <HugeiconsIcon
223
+ icon={EyeIcon}
224
+ className="mr-2 h-4 w-4"
225
+ />
226
+ View
227
+ </Link>
228
+ </DropdownMenuItem>
229
+ <DropdownMenuItem
230
+ className="text-destructive focus:text-destructive"
231
+ onClick={() => {
232
+ setAppToDelete(app)
233
+ setDeleteDialogOpen(true)
234
+ }}
235
+ >
236
+ <HugeiconsIcon
237
+ icon={Delete02Icon}
238
+ className="mr-2 h-4 w-4"
239
+ />
240
+ Delete
241
+ </DropdownMenuItem>
242
+ </DropdownMenuContent>
243
+ </DropdownMenu>
244
+ </TableCell>
245
+ </TableRow>
246
+ ))}
247
+ </TableBody>
248
+ </Table>
249
+ </div>
250
+
251
+ <AlertDialog open={deleteDialogOpen} onOpenChange={setDeleteDialogOpen}>
252
+ <AlertDialogContent>
253
+ <AlertDialogHeader>
254
+ <AlertDialogTitle>Delete App</AlertDialogTitle>
255
+ <AlertDialogDescription>
256
+ Are you sure you want to delete &quot;{appToDelete?.name}&quot;?
257
+ This action cannot be undone and will delete all associated data.
258
+ </AlertDialogDescription>
259
+ </AlertDialogHeader>
260
+ <AlertDialogFooter>
261
+ <AlertDialogCancel>Cancel</AlertDialogCancel>
262
+ <AlertDialogAction
263
+ onClick={handleDelete}
264
+ className="bg-destructive text-destructive-foreground hover:bg-destructive/90"
265
+ disabled={isDeleting}
266
+ >
267
+ {isDeleting ? 'Deleting...' : 'Delete'}
268
+ </AlertDialogAction>
269
+ </AlertDialogFooter>
270
+ </AlertDialogContent>
271
+ </AlertDialog>
272
+ </>
273
+ )
274
+ }
@@ -0,0 +1,24 @@
1
+ {
2
+ "$schema": "https://ui.shadcn.com/schema.json",
3
+ "style": "radix-lyra",
4
+ "rsc": true,
5
+ "tsx": true,
6
+ "tailwind": {
7
+ "config": "",
8
+ "css": "app/globals.css",
9
+ "baseColor": "neutral",
10
+ "cssVariables": true,
11
+ "prefix": ""
12
+ },
13
+ "iconLibrary": "hugeicons",
14
+ "aliases": {
15
+ "components": "@/components",
16
+ "utils": "@/lib/utils",
17
+ "ui": "@/components/ui",
18
+ "lib": "@/lib",
19
+ "hooks": "@/hooks"
20
+ },
21
+ "menuColor": "default",
22
+ "menuAccent": "subtle",
23
+ "registries": {}
24
+ }
@@ -0,0 +1,66 @@
1
+ {
2
+ "name": "@ailog/cli",
3
+ "version": "0.1.1",
4
+ "description": "CLI to run the ailog LLM logging dashboard",
5
+ "private": false,
6
+ "type": "module",
7
+ "bin": {
8
+ "ailog": "./dist/bin/cli.js"
9
+ },
10
+ "files": [
11
+ "dist"
12
+ ],
13
+ "scripts": {
14
+ "dev": "next dev --port 9000",
15
+ "build": "next build",
16
+ "build:cli": "next build && bun scripts/build-cli.ts",
17
+ "start": "next start",
18
+ "start:cli": "node dist/bin/cli.js start",
19
+ "prepublishOnly": "bun run build:cli",
20
+ "lint": "eslint --max-warnings=0 .",
21
+ "lint:fix": "eslint --fix ."
22
+ },
23
+ "dependencies": {
24
+ "commander": "^12.0.0",
25
+ "next": "16.0.10",
26
+ "pg": "^8.16.3",
27
+ "react": "19.2.1",
28
+ "react-dom": "19.2.1",
29
+ "styled-jsx": "^5.1.6",
30
+ "@swc/helpers": "^0.5.15",
31
+ "client-only": "^0.0.1"
32
+ },
33
+ "optionalDependencies": {
34
+ "sharp": "^0.34.5"
35
+ },
36
+ "devDependencies": {
37
+ "@ailog/server": "*",
38
+ "@base-ui/react": "^1.0.0",
39
+ "@hugeicons/core-free-icons": "^2.0.0",
40
+ "@hugeicons/react": "^1.1.1",
41
+ "@tailwindcss/postcss": "^4",
42
+ "@tanstack/react-table": "^8.21.3",
43
+ "@types/bun": "^1.3.5",
44
+ "@types/node": "^20",
45
+ "@types/react": "^19",
46
+ "@types/react-dom": "^19",
47
+ "class-variance-authority": "^0.7.1",
48
+ "clsx": "^2.1.1",
49
+ "eslint": "^9",
50
+ "eslint-config-next": "16.0.10",
51
+ "hono": "^4.11.0",
52
+ "ignore": "^7.0.5",
53
+ "next-themes": "^0.4.6",
54
+ "nuqs": "^2.8.5",
55
+ "radix-ui": "^1.4.3",
56
+ "shadcn": "^3.6.1",
57
+ "sonner": "^2.0.7",
58
+ "tailwind-merge": "^3.4.0",
59
+ "tailwindcss": "^4",
60
+ "tw-animate-css": "^1.4.0",
61
+ "typescript": "^5"
62
+ },
63
+ "engines": {
64
+ "node": ">=18"
65
+ }
66
+ }
@@ -0,0 +1,34 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2017",
4
+ "lib": ["dom", "dom.iterable", "esnext"],
5
+ "allowJs": true,
6
+ "skipLibCheck": true,
7
+ "strict": true,
8
+ "noEmit": true,
9
+ "esModuleInterop": true,
10
+ "module": "esnext",
11
+ "moduleResolution": "bundler",
12
+ "resolveJsonModule": true,
13
+ "isolatedModules": true,
14
+ "jsx": "react-jsx",
15
+ "incremental": true,
16
+ "plugins": [
17
+ {
18
+ "name": "next"
19
+ }
20
+ ],
21
+ "paths": {
22
+ "@/*": ["./*"]
23
+ }
24
+ },
25
+ "include": [
26
+ "next-env.d.ts",
27
+ "**/*.ts",
28
+ "**/*.tsx",
29
+ ".next/types/**/*.ts",
30
+ ".next/dev/types/**/*.ts",
31
+ "**/*.mts"
32
+ ],
33
+ "exclude": ["node_modules"]
34
+ }