claude-session-viewer 0.1.0 → 0.1.1
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/bin/cli.js +9 -2
- package/bin/dev.js +2 -1
- package/dist/client/assets/index-BRGpp7Nq.js +40 -0
- package/dist/client/assets/index-bRG2avxz.css +1 -0
- package/{index.html → dist/client/index.html} +2 -1
- package/dist/server/index.js +364 -0
- package/package.json +14 -3
- package/postcss.config.js +0 -6
- package/src/App.tsx +0 -174
- package/src/components/ProjectGroup.tsx +0 -132
- package/src/components/SessionDetail.tsx +0 -140
- package/src/components/SessionList.tsx +0 -45
- package/src/index.css +0 -55
- package/src/main.tsx +0 -10
- package/src/server/index.ts +0 -440
- package/tailwind.config.js +0 -11
- package/tsconfig.json +0 -31
- package/tsconfig.node.json +0 -10
- package/vite.config.ts +0 -32
|
@@ -1,132 +0,0 @@
|
|
|
1
|
-
import { useState } from 'react'
|
|
2
|
-
import { format } from 'date-fns'
|
|
3
|
-
|
|
4
|
-
interface Session {
|
|
5
|
-
id: string
|
|
6
|
-
project: string
|
|
7
|
-
timestamp: string
|
|
8
|
-
messages: any[]
|
|
9
|
-
messageCount: number
|
|
10
|
-
title?: string
|
|
11
|
-
isAgent?: boolean
|
|
12
|
-
agentSessions?: Session[]
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
interface ProjectGroupProps {
|
|
16
|
-
name: string
|
|
17
|
-
displayName: string
|
|
18
|
-
sessionCount: number
|
|
19
|
-
lastActivity: string
|
|
20
|
-
sessions: Session[]
|
|
21
|
-
selectedId: string | null
|
|
22
|
-
onSelectSession: (id: string) => void
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export default function ProjectGroup({
|
|
26
|
-
displayName,
|
|
27
|
-
sessionCount,
|
|
28
|
-
lastActivity,
|
|
29
|
-
sessions,
|
|
30
|
-
selectedId,
|
|
31
|
-
onSelectSession,
|
|
32
|
-
}: ProjectGroupProps) {
|
|
33
|
-
const [isExpanded, setIsExpanded] = useState(false)
|
|
34
|
-
|
|
35
|
-
return (
|
|
36
|
-
<div className="border-b border-gray-700">
|
|
37
|
-
{/* Project Header */}
|
|
38
|
-
<button
|
|
39
|
-
onClick={() => setIsExpanded(!isExpanded)}
|
|
40
|
-
className="sticky top-0 z-10 w-full text-left p-4 bg-gray-900 hover:bg-gray-800 transition-colors flex items-center justify-between"
|
|
41
|
-
>
|
|
42
|
-
<div className="flex-1 min-w-0">
|
|
43
|
-
<div className="flex items-center gap-2">
|
|
44
|
-
<svg
|
|
45
|
-
className={`w-4 h-4 transition-transform ${isExpanded ? 'rotate-90' : ''}`}
|
|
46
|
-
fill="none"
|
|
47
|
-
viewBox="0 0 24 24"
|
|
48
|
-
stroke="currentColor"
|
|
49
|
-
>
|
|
50
|
-
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M9 5l7 7-7 7" />
|
|
51
|
-
</svg>
|
|
52
|
-
<span className="font-semibold text-sm truncate">{displayName}</span>
|
|
53
|
-
</div>
|
|
54
|
-
<div className="text-xs text-gray-400 mt-1 ml-6">
|
|
55
|
-
{sessionCount} session{sessionCount !== 1 ? 's' : ''} · Last activity{' '}
|
|
56
|
-
{format(new Date(lastActivity), 'PPp')}
|
|
57
|
-
</div>
|
|
58
|
-
</div>
|
|
59
|
-
</button>
|
|
60
|
-
|
|
61
|
-
{/* Sessions List */}
|
|
62
|
-
{isExpanded && (
|
|
63
|
-
<div className="bg-gray-900/50">
|
|
64
|
-
{sessions.map((session) => (
|
|
65
|
-
<div key={session.id}>
|
|
66
|
-
{/* Main Session */}
|
|
67
|
-
<button
|
|
68
|
-
onClick={() => onSelectSession(session.id)}
|
|
69
|
-
className={`w-full text-left px-4 py-3 pl-8 hover:bg-gray-700/50 transition-colors border-l-2 ${
|
|
70
|
-
selectedId === session.id
|
|
71
|
-
? 'bg-gray-700 border-blue-500'
|
|
72
|
-
: 'border-transparent'
|
|
73
|
-
}`}
|
|
74
|
-
>
|
|
75
|
-
{session.title && (
|
|
76
|
-
<div className="text-sm font-medium text-gray-200 mb-1 truncate">
|
|
77
|
-
{session.title}
|
|
78
|
-
</div>
|
|
79
|
-
)}
|
|
80
|
-
<div className="text-xs text-gray-400">
|
|
81
|
-
{format(new Date(session.timestamp), 'PPpp')}
|
|
82
|
-
</div>
|
|
83
|
-
<div className="text-xs text-gray-500 mt-1">
|
|
84
|
-
{session.messageCount} message{session.messageCount !== 1 ? 's' : ''}
|
|
85
|
-
{session.agentSessions && session.agentSessions.length > 0 && (
|
|
86
|
-
<span className="ml-2">
|
|
87
|
-
· {session.agentSessions.length} task{session.agentSessions.length !== 1 ? 's' : ''}
|
|
88
|
-
</span>
|
|
89
|
-
)}
|
|
90
|
-
</div>
|
|
91
|
-
</button>
|
|
92
|
-
|
|
93
|
-
{/* Agent Sessions */}
|
|
94
|
-
{session.agentSessions && session.agentSessions.length > 0 && (
|
|
95
|
-
<div className="bg-gray-900/70">
|
|
96
|
-
{session.agentSessions.map((agentSession) => (
|
|
97
|
-
<button
|
|
98
|
-
key={agentSession.id}
|
|
99
|
-
onClick={() => onSelectSession(agentSession.id)}
|
|
100
|
-
className={`w-full text-left px-4 py-2 pl-14 hover:bg-gray-700/50 transition-colors border-l-2 ${
|
|
101
|
-
selectedId === agentSession.id
|
|
102
|
-
? 'bg-gray-700 border-blue-500'
|
|
103
|
-
: 'border-transparent'
|
|
104
|
-
}`}
|
|
105
|
-
>
|
|
106
|
-
<div className="flex items-center gap-2 mb-1">
|
|
107
|
-
<span className="px-1.5 py-0.5 text-xs bg-purple-900/50 text-purple-300 rounded">
|
|
108
|
-
TASK
|
|
109
|
-
</span>
|
|
110
|
-
{agentSession.title && (
|
|
111
|
-
<span className="text-sm font-medium text-gray-300 truncate">
|
|
112
|
-
{agentSession.title}
|
|
113
|
-
</span>
|
|
114
|
-
)}
|
|
115
|
-
</div>
|
|
116
|
-
<div className="text-xs text-gray-400">
|
|
117
|
-
{format(new Date(agentSession.timestamp), 'PPpp')}
|
|
118
|
-
</div>
|
|
119
|
-
<div className="text-xs text-gray-500 mt-1">
|
|
120
|
-
{agentSession.messageCount} message{agentSession.messageCount !== 1 ? 's' : ''}
|
|
121
|
-
</div>
|
|
122
|
-
</button>
|
|
123
|
-
))}
|
|
124
|
-
</div>
|
|
125
|
-
)}
|
|
126
|
-
</div>
|
|
127
|
-
))}
|
|
128
|
-
</div>
|
|
129
|
-
)}
|
|
130
|
-
</div>
|
|
131
|
-
)
|
|
132
|
-
}
|
|
@@ -1,140 +0,0 @@
|
|
|
1
|
-
import { useQuery } from '@tanstack/react-query'
|
|
2
|
-
import { format } from 'date-fns'
|
|
3
|
-
|
|
4
|
-
interface SessionDetailProps {
|
|
5
|
-
sessionId: string
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
export default function SessionDetail({ sessionId }: SessionDetailProps) {
|
|
9
|
-
const { data, isLoading, error } = useQuery({
|
|
10
|
-
queryKey: ['session', sessionId],
|
|
11
|
-
queryFn: async () => {
|
|
12
|
-
const response = await fetch(`/api/sessions/${sessionId}`)
|
|
13
|
-
if (!response.ok) throw new Error('Failed to fetch session')
|
|
14
|
-
return response.json()
|
|
15
|
-
},
|
|
16
|
-
})
|
|
17
|
-
|
|
18
|
-
if (isLoading) {
|
|
19
|
-
return (
|
|
20
|
-
<div className="flex items-center justify-center h-full">
|
|
21
|
-
<div className="text-gray-400">Loading session...</div>
|
|
22
|
-
</div>
|
|
23
|
-
)
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
if (error) {
|
|
27
|
-
return (
|
|
28
|
-
<div className="flex items-center justify-center h-full">
|
|
29
|
-
<div className="text-red-400">Error: {error.message}</div>
|
|
30
|
-
</div>
|
|
31
|
-
)
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
const session = data?.session
|
|
35
|
-
|
|
36
|
-
return (
|
|
37
|
-
<div className="h-full flex flex-col">
|
|
38
|
-
{/* Header */}
|
|
39
|
-
<div className="border-b border-gray-700 p-6 bg-gray-800">
|
|
40
|
-
<div className="flex items-center gap-2">
|
|
41
|
-
{session?.isAgent && (
|
|
42
|
-
<span className="px-2 py-1 text-xs bg-purple-900/50 text-purple-300 rounded font-semibold">
|
|
43
|
-
TASK
|
|
44
|
-
</span>
|
|
45
|
-
)}
|
|
46
|
-
<h2 className="text-2xl font-bold truncate flex-1">{session?.title || 'Untitled Session'}</h2>
|
|
47
|
-
</div>
|
|
48
|
-
<div className="text-xl text-gray-300">{session?.project}</div>
|
|
49
|
-
<div className="flex items-center gap-3 mt-4 text-sm text-gray-400">
|
|
50
|
-
<span>
|
|
51
|
-
{session?.timestamp && format(new Date(session.timestamp), 'PPpp')}
|
|
52
|
-
</span>
|
|
53
|
-
<span>•</span>
|
|
54
|
-
<span>
|
|
55
|
-
{session?.messageCount || 0} message{session?.messageCount !== 1 ? 's' : ''}
|
|
56
|
-
</span>
|
|
57
|
-
{session?.agentSessions && session.agentSessions.length > 0 && (
|
|
58
|
-
<>
|
|
59
|
-
<span>•</span>
|
|
60
|
-
<span>
|
|
61
|
-
{session.agentSessions.length} task{session.agentSessions.length !== 1 ? 's' : ''}
|
|
62
|
-
</span>
|
|
63
|
-
</>
|
|
64
|
-
)}
|
|
65
|
-
</div>
|
|
66
|
-
</div>
|
|
67
|
-
|
|
68
|
-
{/* Messages Timeline */}
|
|
69
|
-
<div className="flex-1 overflow-y-auto p-6">
|
|
70
|
-
<div className="max-w-4xl mx-auto space-y-6">
|
|
71
|
-
{session?.messages.map((message: any, index: number) => (
|
|
72
|
-
<div key={index} className="border-l-2 border-gray-700 pl-4">
|
|
73
|
-
<div className="flex items-start justify-between mb-2">
|
|
74
|
-
<div className="flex items-center gap-2">
|
|
75
|
-
<span
|
|
76
|
-
className={`px-2 py-1 text-xs rounded ${
|
|
77
|
-
message.type === 'user'
|
|
78
|
-
? 'bg-blue-900 text-blue-200'
|
|
79
|
-
: message.type === 'assistant'
|
|
80
|
-
? 'bg-green-900 text-green-200'
|
|
81
|
-
: 'bg-gray-700 text-gray-300'
|
|
82
|
-
}`}
|
|
83
|
-
>
|
|
84
|
-
{message.type || 'system'}
|
|
85
|
-
</span>
|
|
86
|
-
{message.timestamp && (
|
|
87
|
-
<span className="text-xs text-gray-500">
|
|
88
|
-
{format(new Date(message.timestamp), 'HH:mm:ss')}
|
|
89
|
-
</span>
|
|
90
|
-
)}
|
|
91
|
-
</div>
|
|
92
|
-
</div>
|
|
93
|
-
|
|
94
|
-
{/* Message Content */}
|
|
95
|
-
<div className="bg-gray-800 rounded-lg p-4 text-sm">
|
|
96
|
-
{message.message?.content && Array.isArray(message.message.content) ? (
|
|
97
|
-
<div className="space-y-2">
|
|
98
|
-
{message.message.content.map((content: any, idx: number) => (
|
|
99
|
-
<div key={idx}>
|
|
100
|
-
{content.type === 'text' && (
|
|
101
|
-
<p className="whitespace-pre-wrap">{content.text}</p>
|
|
102
|
-
)}
|
|
103
|
-
{content.type === 'tool_use' && (
|
|
104
|
-
<div className="bg-gray-900 p-3 rounded border border-gray-700">
|
|
105
|
-
<div className="text-yellow-400 font-mono text-xs mb-2">
|
|
106
|
-
🔧 {content.name}
|
|
107
|
-
</div>
|
|
108
|
-
<pre className="text-xs overflow-x-auto text-gray-400">
|
|
109
|
-
{JSON.stringify(content.input, null, 2)}
|
|
110
|
-
</pre>
|
|
111
|
-
</div>
|
|
112
|
-
)}
|
|
113
|
-
{content.type === 'tool_result' && (
|
|
114
|
-
<div className="bg-gray-900 p-3 rounded border border-gray-700">
|
|
115
|
-
<div className="text-green-400 font-mono text-xs mb-2">
|
|
116
|
-
✓ Tool Result
|
|
117
|
-
</div>
|
|
118
|
-
<pre className="text-xs overflow-x-auto text-gray-400">
|
|
119
|
-
{typeof content.content === 'string'
|
|
120
|
-
? content.content
|
|
121
|
-
: JSON.stringify(content.content, null, 2)}
|
|
122
|
-
</pre>
|
|
123
|
-
</div>
|
|
124
|
-
)}
|
|
125
|
-
</div>
|
|
126
|
-
))}
|
|
127
|
-
</div>
|
|
128
|
-
) : (
|
|
129
|
-
<pre className="text-xs overflow-x-auto text-gray-400">
|
|
130
|
-
{JSON.stringify(message, null, 2)}
|
|
131
|
-
</pre>
|
|
132
|
-
)}
|
|
133
|
-
</div>
|
|
134
|
-
</div>
|
|
135
|
-
))}
|
|
136
|
-
</div>
|
|
137
|
-
</div>
|
|
138
|
-
</div>
|
|
139
|
-
)
|
|
140
|
-
}
|
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
import ProjectGroup from './ProjectGroup'
|
|
2
|
-
|
|
3
|
-
interface Session {
|
|
4
|
-
id: string
|
|
5
|
-
project: string
|
|
6
|
-
timestamp: string
|
|
7
|
-
messages: any[]
|
|
8
|
-
messageCount: number
|
|
9
|
-
title?: string
|
|
10
|
-
isAgent?: boolean
|
|
11
|
-
agentSessions?: Session[]
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
interface ProjectGroup {
|
|
15
|
-
name: string
|
|
16
|
-
displayName: string
|
|
17
|
-
sessionCount: number
|
|
18
|
-
lastActivity: string
|
|
19
|
-
sessions: Session[]
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
interface SessionListProps {
|
|
23
|
-
projects: ProjectGroup[]
|
|
24
|
-
selectedId: string | null
|
|
25
|
-
onSelect: (id: string) => void
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
export default function SessionList({ projects, selectedId, onSelect }: SessionListProps) {
|
|
29
|
-
return (
|
|
30
|
-
<div>
|
|
31
|
-
{projects.map((project) => (
|
|
32
|
-
<ProjectGroup
|
|
33
|
-
key={project.name}
|
|
34
|
-
name={project.name}
|
|
35
|
-
displayName={project.displayName}
|
|
36
|
-
sessionCount={project.sessionCount}
|
|
37
|
-
lastActivity={project.lastActivity}
|
|
38
|
-
sessions={project.sessions}
|
|
39
|
-
selectedId={selectedId}
|
|
40
|
-
onSelectSession={onSelect}
|
|
41
|
-
/>
|
|
42
|
-
))}
|
|
43
|
-
</div>
|
|
44
|
-
)
|
|
45
|
-
}
|
package/src/index.css
DELETED
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
@tailwind base;
|
|
2
|
-
@tailwind components;
|
|
3
|
-
@tailwind utilities;
|
|
4
|
-
|
|
5
|
-
:root {
|
|
6
|
-
font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
|
|
7
|
-
line-height: 1.5;
|
|
8
|
-
font-weight: 400;
|
|
9
|
-
|
|
10
|
-
color-scheme: light dark;
|
|
11
|
-
color: rgba(255, 255, 255, 0.87);
|
|
12
|
-
background-color: #242424;
|
|
13
|
-
|
|
14
|
-
font-synthesis: none;
|
|
15
|
-
text-rendering: optimizeLegibility;
|
|
16
|
-
-webkit-font-smoothing: antialiased;
|
|
17
|
-
-moz-osx-font-smoothing: grayscale;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
body {
|
|
21
|
-
margin: 0;
|
|
22
|
-
display: flex;
|
|
23
|
-
place-items: center;
|
|
24
|
-
min-width: 320px;
|
|
25
|
-
min-height: 100vh;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
#root {
|
|
29
|
-
width: 100%;
|
|
30
|
-
min-height: 100vh;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
/* Custom scrollbar styles */
|
|
34
|
-
* {
|
|
35
|
-
scrollbar-width: thin;
|
|
36
|
-
scrollbar-color: #4B5563 #1F2937;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
*::-webkit-scrollbar {
|
|
40
|
-
width: 8px;
|
|
41
|
-
height: 8px;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
*::-webkit-scrollbar-track {
|
|
45
|
-
background: #1F2937;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
*::-webkit-scrollbar-thumb {
|
|
49
|
-
background-color: #4B5563;
|
|
50
|
-
border-radius: 4px;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
*::-webkit-scrollbar-thumb:hover {
|
|
54
|
-
background-color: #6B7280;
|
|
55
|
-
}
|
package/src/main.tsx
DELETED