dev3000 0.0.22 → 0.0.24

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.
@@ -0,0 +1,274 @@
1
+ 'use client';
2
+
3
+ import { useState, useEffect } from 'react';
4
+
5
+ interface InteractionEvent {
6
+ timestamp: string;
7
+ type: 'CLICK' | 'TAP' | 'SCROLL' | 'KEY';
8
+ x?: number;
9
+ y?: number;
10
+ target?: string;
11
+ direction?: string;
12
+ distance?: number;
13
+ key?: string;
14
+ }
15
+
16
+ interface NavigationEvent {
17
+ timestamp: string;
18
+ url: string;
19
+ }
20
+
21
+ interface ScreenshotEvent {
22
+ timestamp: string;
23
+ url: string;
24
+ event: string;
25
+ }
26
+
27
+ interface ReplayData {
28
+ interactions: InteractionEvent[];
29
+ navigations: NavigationEvent[];
30
+ screenshots: ScreenshotEvent[];
31
+ startTime: string;
32
+ endTime: string;
33
+ duration: number;
34
+ }
35
+
36
+ export default function ReplayClient() {
37
+ const [replayData, setReplayData] = useState<ReplayData | null>(null);
38
+ const [loading, setLoading] = useState(false);
39
+ const [selectedTimeRange, setSelectedTimeRange] = useState<{
40
+ start: string;
41
+ end: string;
42
+ } | null>(null);
43
+ const [playbackSpeed, setPlaybackSpeed] = useState(1);
44
+ const [isPlaying, setIsPlaying] = useState(false);
45
+ const [currentEventIndex, setCurrentEventIndex] = useState(0);
46
+
47
+ const loadReplayData = async () => {
48
+ setLoading(true);
49
+ try {
50
+ const params = new URLSearchParams({ action: 'parse' });
51
+ if (selectedTimeRange) {
52
+ params.set('startTime', selectedTimeRange.start);
53
+ params.set('endTime', selectedTimeRange.end);
54
+ }
55
+
56
+ const response = await fetch(`/api/replay?${params}`);
57
+ const data = await response.json();
58
+
59
+ if (response.ok) {
60
+ setReplayData(data);
61
+ } else {
62
+ console.error('Failed to load replay data:', data.error);
63
+ }
64
+ } catch (error) {
65
+ console.error('Error loading replay data:', error);
66
+ }
67
+ setLoading(false);
68
+ };
69
+
70
+ useEffect(() => {
71
+ loadReplayData();
72
+ }, [selectedTimeRange]);
73
+
74
+ const allEvents = replayData ? [
75
+ ...replayData.interactions.map(i => ({ ...i, eventType: 'interaction' as const })),
76
+ ...replayData.navigations.map(n => ({ ...n, eventType: 'navigation' as const }))
77
+ ].sort((a, b) => new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime()) : [];
78
+
79
+ const formatTimestamp = (timestamp: string) => {
80
+ return new Date(timestamp).toLocaleTimeString();
81
+ };
82
+
83
+ const formatDuration = (ms: number) => {
84
+ const seconds = Math.floor(ms / 1000);
85
+ const minutes = Math.floor(seconds / 60);
86
+ return `${minutes}m ${seconds % 60}s`;
87
+ };
88
+
89
+ const startReplay = async () => {
90
+ if (!replayData) return;
91
+
92
+ setIsPlaying(true);
93
+ setCurrentEventIndex(0);
94
+
95
+ // This is a simplified preview - in a full implementation,
96
+ // this would actually control a browser session
97
+ for (let i = 0; i < allEvents.length; i++) {
98
+ if (!isPlaying) break;
99
+
100
+ setCurrentEventIndex(i);
101
+
102
+ const event = allEvents[i];
103
+ const nextEvent = allEvents[i + 1];
104
+
105
+ if (nextEvent) {
106
+ const currentTime = new Date(event.timestamp).getTime();
107
+ const nextTime = new Date(nextEvent.timestamp).getTime();
108
+ const delay = (nextTime - currentTime) / playbackSpeed;
109
+
110
+ await new Promise(resolve => setTimeout(resolve, Math.min(delay, 5000))); // Cap at 5s
111
+ }
112
+ }
113
+
114
+ setIsPlaying(false);
115
+ };
116
+
117
+ const stopReplay = () => {
118
+ setIsPlaying(false);
119
+ };
120
+
121
+ if (loading) {
122
+ return (
123
+ <div className="min-h-screen bg-gray-50 flex items-center justify-center">
124
+ <div className="text-center">
125
+ <div className="inline-block animate-spin rounded-full h-8 w-8 border-b-2 border-blue-600"></div>
126
+ <div className="text-gray-500 text-sm mt-4">Loading replay data...</div>
127
+ </div>
128
+ </div>
129
+ );
130
+ }
131
+
132
+ return (
133
+ <div className="min-h-screen bg-gray-50">
134
+ <div className="bg-white shadow-sm border-b">
135
+ <div className="max-w-7xl mx-auto px-4 py-3">
136
+ <div className="flex items-center justify-between">
137
+ <div className="flex items-center gap-4">
138
+ <h1 className="text-2xl font-bold text-gray-900">Session Replay</h1>
139
+ {replayData && (
140
+ <div className="flex items-center gap-4 text-sm text-gray-600">
141
+ <span>{replayData.interactions.length} interactions</span>
142
+ <span>{replayData.navigations.length} navigations</span>
143
+ <span>{replayData.screenshots.length} screenshots</span>
144
+ <span>{formatDuration(replayData.duration)} duration</span>
145
+ </div>
146
+ )}
147
+ </div>
148
+
149
+ <div className="flex items-center gap-2">
150
+ <select
151
+ value={playbackSpeed}
152
+ onChange={(e) => setPlaybackSpeed(Number(e.target.value))}
153
+ className="px-3 py-1 border border-gray-300 rounded text-sm"
154
+ >
155
+ <option value={0.5}>0.5x</option>
156
+ <option value={1}>1x</option>
157
+ <option value={2}>2x</option>
158
+ <option value={4}>4x</option>
159
+ <option value={8}>8x</option>
160
+ </select>
161
+
162
+ {!isPlaying ? (
163
+ <button
164
+ onClick={startReplay}
165
+ disabled={!replayData || allEvents.length === 0}
166
+ className="px-4 py-2 bg-blue-600 text-white rounded hover:bg-blue-700 disabled:opacity-50 disabled:cursor-not-allowed"
167
+ >
168
+ ▶ Play
169
+ </button>
170
+ ) : (
171
+ <button
172
+ onClick={stopReplay}
173
+ className="px-4 py-2 bg-red-600 text-white rounded hover:bg-red-700"
174
+ >
175
+ ⏹ Stop
176
+ </button>
177
+ )}
178
+ </div>
179
+ </div>
180
+ </div>
181
+ </div>
182
+
183
+ <div className="max-w-7xl mx-auto px-4 py-6">
184
+ {!replayData ? (
185
+ <div className="text-center py-12">
186
+ <div className="text-gray-400 text-lg">No replay data available</div>
187
+ <div className="text-gray-500 text-sm mt-2">
188
+ Start using your app to generate interaction data
189
+ </div>
190
+ </div>
191
+ ) : (
192
+ <div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
193
+ {/* Timeline */}
194
+ <div className="bg-white rounded-lg shadow-sm border">
195
+ <div className="px-4 py-3 border-b">
196
+ <h2 className="font-semibold text-gray-900">Event Timeline</h2>
197
+ </div>
198
+ <div className="max-h-96 overflow-y-auto">
199
+ {allEvents.map((event, index) => (
200
+ <div
201
+ key={index}
202
+ className={`px-4 py-2 border-b last:border-b-0 ${
203
+ index === currentEventIndex ? 'bg-blue-50 border-blue-200' : ''
204
+ }`}
205
+ >
206
+ <div className="flex items-center justify-between">
207
+ <div className="flex items-center gap-2">
208
+ <span className="text-xs font-mono text-gray-500">
209
+ {formatTimestamp(event.timestamp)}
210
+ </span>
211
+ <span className={`px-2 py-1 rounded text-xs font-medium ${
212
+ event.eventType === 'navigation'
213
+ ? 'bg-purple-100 text-purple-800'
214
+ : event.type === 'CLICK'
215
+ ? 'bg-green-100 text-green-800'
216
+ : event.type === 'SCROLL'
217
+ ? 'bg-yellow-100 text-yellow-800'
218
+ : 'bg-gray-100 text-gray-800'
219
+ }`}>
220
+ {event.eventType === 'navigation' ? 'NAV' : event.type}
221
+ </span>
222
+ </div>
223
+ {isPlaying && index === currentEventIndex && (
224
+ <div className="w-2 h-2 bg-blue-500 rounded-full animate-pulse"></div>
225
+ )}
226
+ </div>
227
+ <div className="mt-1 text-sm text-gray-700">
228
+ {event.eventType === 'navigation' ? (
229
+ <span>→ {event.url}</span>
230
+ ) : event.type === 'CLICK' || event.type === 'TAP' ? (
231
+ <span>({event.x}, {event.y}) on {event.target}</span>
232
+ ) : event.type === 'SCROLL' ? (
233
+ <span>{event.direction} {event.distance}px to ({event.x}, {event.y})</span>
234
+ ) : event.type === 'KEY' ? (
235
+ <span>"{event.key}" in {event.target}</span>
236
+ ) : (
237
+ <span>{JSON.stringify(event)}</span>
238
+ )}
239
+ </div>
240
+ </div>
241
+ ))}
242
+ </div>
243
+ </div>
244
+
245
+ {/* Screenshots */}
246
+ <div className="bg-white rounded-lg shadow-sm border">
247
+ <div className="px-4 py-3 border-b">
248
+ <h2 className="font-semibold text-gray-900">Screenshots</h2>
249
+ </div>
250
+ <div className="max-h-96 overflow-y-auto">
251
+ {replayData.screenshots.map((screenshot, index) => (
252
+ <div key={index} className="px-4 py-2 border-b last:border-b-0">
253
+ <div className="flex items-center justify-between mb-2">
254
+ <span className="text-xs font-mono text-gray-500">
255
+ {formatTimestamp(screenshot.timestamp)}
256
+ </span>
257
+ <span className="text-xs text-gray-600">{screenshot.event}</span>
258
+ </div>
259
+ <img
260
+ src={screenshot.url}
261
+ alt={`Screenshot: ${screenshot.event}`}
262
+ className="w-full rounded border"
263
+ style={{ maxHeight: '200px', objectFit: 'contain' }}
264
+ />
265
+ </div>
266
+ ))}
267
+ </div>
268
+ </div>
269
+ </div>
270
+ )}
271
+ </div>
272
+ </div>
273
+ );
274
+ }
@@ -0,0 +1,5 @@
1
+ import ReplayClient from './ReplayClient';
2
+
3
+ export default function ReplayPage() {
4
+ return <ReplayClient />;
5
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dev3000",
3
- "version": "0.0.22",
3
+ "version": "0.0.24",
4
4
  "description": "AI-powered development tools with browser monitoring and MCP server integration",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",