coaia-visualizer 1.4.3 → 1.5.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/.dockerignore ADDED
@@ -0,0 +1,9 @@
1
+ node_modules
2
+ dist
3
+ .next
4
+ .git
5
+ .env.local
6
+ .env*.local
7
+ test-results
8
+ *.log
9
+ .DS_Store
package/Dockerfile.app ADDED
@@ -0,0 +1,50 @@
1
+ # Build stage
2
+ FROM node:20-alpine AS builder
3
+
4
+ WORKDIR /app
5
+
6
+ # Copy package files
7
+ COPY package.json pnpm-lock.yaml ./
8
+
9
+ # Install pnpm and dependencies
10
+ RUN npm install -g pnpm@latest && \
11
+ pnpm install --frozen-lockfile
12
+
13
+ # Copy application code
14
+ COPY . .
15
+
16
+ # Build Next.js application
17
+ RUN pnpm build
18
+
19
+ # Production stage
20
+ FROM node:20-alpine AS runner
21
+
22
+ WORKDIR /app
23
+
24
+ # Install pnpm
25
+ RUN npm install -g pnpm@latest
26
+
27
+ # Copy package files
28
+ COPY package.json pnpm-lock.yaml ./
29
+
30
+ # Install production dependencies only, skip prepare hook
31
+ RUN pnpm install --prod --frozen-lockfile --ignore-scripts
32
+
33
+ # Copy built application from builder
34
+ COPY --from=builder /app/.next ./.next
35
+ COPY --from=builder /app/public ./public
36
+ COPY --from=builder /app/next.config.mjs ./
37
+ COPY --from=builder /app/app ./app
38
+ COPY --from=builder /app/components ./components
39
+ COPY --from=builder /app/lib ./lib
40
+ COPY --from=builder /app/dist ./dist
41
+
42
+ # Create data directories
43
+ RUN mkdir -p /app/samples /app/test-data
44
+
45
+ ENV NODE_ENV=production
46
+ ENV PORT=4321
47
+
48
+ EXPOSE 4321
49
+
50
+ CMD ["pnpm", "start"]
@@ -0,0 +1,24 @@
1
+ FROM node:20-alpine
2
+
3
+ WORKDIR /test
4
+
5
+ # Install system dependencies
6
+ RUN apk add --no-cache \
7
+ curl \
8
+ jq \
9
+ bash \
10
+ git \
11
+ python3 \
12
+ py3-pip
13
+
14
+ # Create directories
15
+ RUN mkdir -p /test-scripts /test-results /test-data
16
+
17
+ # Copy test scripts
18
+ COPY test-scripts /test-scripts
19
+
20
+ # Make scripts executable
21
+ RUN chmod +x /test-scripts/*.sh
22
+
23
+ # Default command
24
+ CMD ["/bin/bash"]
@@ -0,0 +1,435 @@
1
+ # 🎬 COAIA Visualizer: Live Narrative Observer Mode
2
+
3
+ ## Vision
4
+
5
+ Transform coaia-visualizer into a **real-time narrative observatory** that watches structural tension charts being created, displays them as they emerge, and creates an immersive audio-visual experience.
6
+
7
+ ## Current State
8
+
9
+ - ✅ Next.js app with JSONL parsing
10
+ - ✅ Narrative beat display components
11
+ - ✅ Manual reload button
12
+ - ✅ Chart editing capabilities
13
+ - ✅ CLI launcher with memory-path flag
14
+
15
+ ## Target State
16
+
17
+ - 🎯 Live monitoring mode with auto-refresh
18
+ - 🎯 Audio file integration with auto-play
19
+ - 🎯 Timeline visualization
20
+ - 🎯 Real-time beat appearance animations
21
+ - 🎯 Observer dashboard for multiple sessions
22
+
23
+ ## Architecture
24
+
25
+ ```
26
+ ┌──────────────────────────────────────────────────┐
27
+ │ Session: Creating Narrative Beats │
28
+ │ ├─ stc_observer.sh --read │
29
+ │ │ ├─ Watches: _claude_PreToolUse.jsonl │
30
+ │ │ ├─ Processes: MCP tool calls │
31
+ │ │ ├─ Generates: audio/{timestamp}.mp3/.txt │
32
+ │ │ └─ Appends: charts_*.coaia-narrative.jsonl │
33
+ │ └─ Outputs to: workspace/.coaia/*.jsonl │
34
+ └──────────────────────────────────────────────────┘
35
+ ↓ (polling every 2s)
36
+ ┌──────────────────────────────────────────────────┐
37
+ │ coaia-visualizer --live │
38
+ │ ├─ Polls JSONL for changes │
39
+ │ ├─ Detects new beats (via beatCount) │
40
+ │ ├─ Auto-scrolls to latest │
41
+ │ ├─ Plays audio/{timestamp}.mp3 (auto-play) │
42
+ │ ├─ Shows timeline with act progression │
43
+ │ └─ Live indicator (pulse animation) │
44
+ └──────────────────────────────────────────────────┘
45
+ ```
46
+
47
+ ## Implementation Plan
48
+
49
+ ### Phase 1: Live Monitoring Core
50
+
51
+ #### 1.1. CLI Enhancement
52
+ **File:** `cli.ts`
53
+
54
+ ```typescript
55
+ // New flags
56
+ --live Enable live monitoring mode
57
+ --poll-interval MS Polling interval (default: 2000ms)
58
+ --auto-play Auto-play audio for new beats
59
+ --audio-dir PATH Audio directory (default: ./audio)
60
+ ```
61
+
62
+ **Environment Variables:**
63
+ ```bash
64
+ COAIAV_LIVE=true
65
+ COAIAV_POLL_INTERVAL=2000
66
+ COAIAV_AUTO_PLAY=true
67
+ COAIAV_AUDIO_DIR=./audio
68
+ ```
69
+
70
+ #### 1.2. Watch API Route
71
+ **File:** `app/api/watch/route.ts`
72
+
73
+ ```typescript
74
+ export async function GET() {
75
+ const memoryPath = process.env.COAIAV_MEMORY_PATH;
76
+ const stats = await fs.stat(memoryPath);
77
+ const content = await fs.readFile(memoryPath, 'utf-8');
78
+ const lines = content.trim().split('\n');
79
+
80
+ // Count beats
81
+ const beatCount = lines.filter(line => {
82
+ const record = JSON.parse(line);
83
+ return record.type === 'entity' &&
84
+ record.metadata?.type === 'narrative_beat';
85
+ }).length;
86
+
87
+ return NextResponse.json({
88
+ lastModified: stats.mtime.getTime(),
89
+ beatCount,
90
+ fileSize: stats.size
91
+ });
92
+ }
93
+ ```
94
+
95
+ #### 1.3. Audio API Route
96
+ **File:** `app/api/audio/[timestamp]/route.ts`
97
+
98
+ ```typescript
99
+ export async function GET(
100
+ request: NextRequest,
101
+ { params }: { params: { timestamp: string } }
102
+ ) {
103
+ const audioDir = process.env.COAIAV_AUDIO_DIR || './audio';
104
+ const audioPath = path.join(audioDir, `${params.timestamp}.mp3`);
105
+
106
+ const file = await fs.readFile(audioPath);
107
+ return new NextResponse(file, {
108
+ headers: {
109
+ 'Content-Type': 'audio/mpeg',
110
+ 'Content-Length': file.length.toString()
111
+ }
112
+ });
113
+ }
114
+ ```
115
+
116
+ ### Phase 2: Live Mode UI
117
+
118
+ #### 2.1. Live Monitor Hook
119
+ **File:** `hooks/use-live-monitor.ts`
120
+
121
+ ```typescript
122
+ export function useLiveMonitor(options: {
123
+ enabled: boolean;
124
+ pollInterval: number;
125
+ onNewBeat: (beat: EntityRecord) => void;
126
+ }) {
127
+ const [lastBeatCount, setLastBeatCount] = useState(0);
128
+ const [isLive, setIsLive] = useState(false);
129
+
130
+ useEffect(() => {
131
+ if (!options.enabled) return;
132
+
133
+ const interval = setInterval(async () => {
134
+ const response = await fetch('/api/watch');
135
+ const { beatCount } = await response.json();
136
+
137
+ if (beatCount > lastBeatCount) {
138
+ setIsLive(true);
139
+ // Trigger reload and notify about new beats
140
+ const newBeatsCount = beatCount - lastBeatCount;
141
+ // Reload data and call onNewBeat for each new beat
142
+ setLastBeatCount(beatCount);
143
+ }
144
+ }, options.pollInterval);
145
+
146
+ return () => clearInterval(interval);
147
+ }, [options.enabled, options.pollInterval, lastBeatCount]);
148
+
149
+ return { isLive };
150
+ }
151
+ ```
152
+
153
+ #### 2.2. Audio Player Component
154
+ **File:** `components/audio-player.tsx`
155
+
156
+ ```typescript
157
+ export function AudioPlayer({
158
+ audioFiles,
159
+ autoPlay = false,
160
+ currentBeatIndex
161
+ }: AudioPlayerProps) {
162
+ const audioRef = useRef<HTMLAudioElement>(null);
163
+ const [isPlaying, setIsPlaying] = useState(false);
164
+ const [currentTrack, setCurrentTrack] = useState(0);
165
+
166
+ useEffect(() => {
167
+ if (autoPlay && audioRef.current && currentTrack < audioFiles.length) {
168
+ audioRef.current.play();
169
+ }
170
+ }, [currentTrack, autoPlay]);
171
+
172
+ return (
173
+ <div className="audio-player">
174
+ <audio
175
+ ref={audioRef}
176
+ src={`/api/audio/${audioFiles[currentTrack]}`}
177
+ onEnded={() => setCurrentTrack(prev => prev + 1)}
178
+ onPlay={() => setIsPlaying(true)}
179
+ onPause={() => setIsPlaying(false)}
180
+ />
181
+ {/* Playback controls */}
182
+ </div>
183
+ );
184
+ }
185
+ ```
186
+
187
+ #### 2.3. Timeline Component
188
+ **File:** `components/narrative-timeline.tsx`
189
+
190
+ ```typescript
191
+ export function NarrativeTimeline({ beats }: { beats: EntityRecord[] }) {
192
+ const containerRef = useRef<HTMLDivElement>(null);
193
+
194
+ useEffect(() => {
195
+ // Auto-scroll to latest beat
196
+ if (containerRef.current) {
197
+ containerRef.current.scrollTop = containerRef.current.scrollHeight;
198
+ }
199
+ }, [beats.length]);
200
+
201
+ return (
202
+ <div ref={containerRef} className="timeline-container">
203
+ {beats.map((beat, idx) => (
204
+ <TimelineBeat
205
+ key={beat.name}
206
+ beat={beat}
207
+ index={idx}
208
+ isLatest={idx === beats.length - 1}
209
+ />
210
+ ))}
211
+ </div>
212
+ );
213
+ }
214
+ ```
215
+
216
+ #### 2.4. Live Indicator
217
+ **File:** `components/live-indicator.tsx`
218
+
219
+ ```typescript
220
+ export function LiveIndicator({ isLive }: { isLive: boolean }) {
221
+ return (
222
+ <div className="flex items-center gap-2">
223
+ <div className={`w-2 h-2 rounded-full ${
224
+ isLive ? 'bg-red-500 animate-pulse' : 'bg-gray-400'
225
+ }`} />
226
+ <span className="text-sm font-medium">
227
+ {isLive ? 'LIVE' : 'OFFLINE'}
228
+ </span>
229
+ </div>
230
+ );
231
+ }
232
+ ```
233
+
234
+ ### Phase 3: Enhanced Page Component
235
+
236
+ #### 3.1. Live Mode Page
237
+ **File:** `app/live/page.tsx`
238
+
239
+ ```typescript
240
+ "use client"
241
+
242
+ export default function LivePage() {
243
+ const [beats, setBeats] = useState<EntityRecord[]>([]);
244
+ const [audioQueue, setAudioQueue] = useState<string[]>([]);
245
+ const isLiveMode = process.env.NEXT_PUBLIC_LIVE_MODE === 'true';
246
+ const pollInterval = parseInt(process.env.NEXT_PUBLIC_POLL_INTERVAL || '2000');
247
+
248
+ const { isLive } = useLiveMonitor({
249
+ enabled: isLiveMode,
250
+ pollInterval,
251
+ onNewBeat: (beat) => {
252
+ setBeats(prev => [...prev, beat]);
253
+ // Extract audio timestamp from beat metadata
254
+ const timestamp = beat.metadata.timestamp;
255
+ if (timestamp) {
256
+ setAudioQueue(prev => [...prev, timestamp]);
257
+ }
258
+ }
259
+ });
260
+
261
+ return (
262
+ <div className="min-h-screen bg-background">
263
+ <header className="border-b sticky top-0 z-10 bg-card">
264
+ <div className="container mx-auto px-4 py-4">
265
+ <div className="flex items-center justify-between">
266
+ <h1 className="text-2xl font-bold">
267
+ 🎬 Live Narrative Observer
268
+ </h1>
269
+ <LiveIndicator isLive={isLive} />
270
+ </div>
271
+ </div>
272
+ </header>
273
+
274
+ <main className="container mx-auto px-4 py-8">
275
+ <div className="grid grid-cols-1 lg:grid-cols-3 gap-6">
276
+ {/* Timeline */}
277
+ <div className="lg:col-span-1">
278
+ <NarrativeTimeline beats={beats} />
279
+ </div>
280
+
281
+ {/* Beat Details */}
282
+ <div className="lg:col-span-2">
283
+ <NarrativeBeats beats={beats} />
284
+ </div>
285
+ </div>
286
+ </main>
287
+
288
+ {/* Audio Player */}
289
+ <AudioPlayer
290
+ audioFiles={audioQueue}
291
+ autoPlay={true}
292
+ currentBeatIndex={beats.length - 1}
293
+ />
294
+ </div>
295
+ );
296
+ }
297
+ ```
298
+
299
+ ## Usage Examples
300
+
301
+ ### Example 1: Basic Live Mode
302
+ ```bash
303
+ # Terminal 1: Start observer with audio
304
+ cd /a/src/_sessiondata/55158baa-dea7-45c8-9941-540433cfe551
305
+ ./scripts/stc_observer.sh --read
306
+
307
+ # Terminal 2: Start visualizer in live mode
308
+ cd /src/coaia-visualizer
309
+ npx coaia-visualizer --live \
310
+ --memory-path /home/mia/workspace/stcmastery-copilot-acp-55158baa-dea7-45c8-9941-540433cfe551--2602072108/.coaia/stcmastery-copilot-acp-55158baa-dea7-45c8-9941-540433cfe551--2602072108.jsonl \
311
+ --audio-dir /a/src/_sessiondata/55158baa-dea7-45c8-9941-540433cfe551/audio \
312
+ --auto-play
313
+ ```
314
+
315
+ ### Example 2: With Environment Variables
316
+ ```bash
317
+ export COAIAV_LIVE=true
318
+ export COAIAV_POLL_INTERVAL=3000
319
+ export COAIAV_AUTO_PLAY=true
320
+ export COAIAN_MF=/home/mia/workspace/stcmastery-copilot-acp-55158baa-dea7-45c8-9941-540433cfe551--2602072108/.coaia/stcmastery-copilot-acp-55158baa-dea7-45c8-9941-540433cfe551--2602072108.jsonl
321
+ export COAIAV_AUDIO_DIR=/a/src/_sessiondata/55158baa-dea7-45c8-9941-540433cfe551/audio
322
+
323
+ npx coaia-visualizer
324
+ ```
325
+
326
+ ### Example 3: Launch Script
327
+ **File:** `/src/coaia-visualizer/launch-live.sh`
328
+
329
+ ```bash
330
+ #!/bin/bash
331
+
332
+ SESSION_ID="55158baa-dea7-45c8-9941-540433cfe551"
333
+ SESSION_DIR="/a/src/_sessiondata/${SESSION_ID}"
334
+ WORKSPACE_DIR="/home/mia/workspace/stcmastery-copilot-acp-${SESSION_ID}--2602072108"
335
+
336
+ # Start observer in background
337
+ cd "$SESSION_DIR"
338
+ ./scripts/stc_observer.sh --read &
339
+ OBSERVER_PID=$!
340
+
341
+ # Start visualizer in live mode
342
+ cd /src/coaia-visualizer
343
+ COAIAV_LIVE=true \
344
+ COAIAV_AUTO_PLAY=true \
345
+ COAIAN_MF="${WORKSPACE_DIR}/.coaia/stcmastery-copilot-acp-${SESSION_ID}--2602072108.jsonl" \
346
+ COAIAV_AUDIO_DIR="${SESSION_DIR}/audio" \
347
+ npx coaia-visualizer
348
+
349
+ # Cleanup
350
+ kill $OBSERVER_PID
351
+ ```
352
+
353
+ ## Features Roadmap
354
+
355
+ ### Phase 1 (Core) ✨
356
+ - [ ] File polling mechanism
357
+ - [ ] Live mode flag in CLI
358
+ - [ ] Auto-reload on JSONL changes
359
+ - [ ] Audio player component
360
+ - [ ] Auto-play integration
361
+
362
+ ### Phase 2 (Enhanced) 🎨
363
+ - [ ] Timeline visualization
364
+ - [ ] Beat appearance animations
365
+ - [ ] Auto-scroll to latest
366
+ - [ ] Live indicator with pulse
367
+ - [ ] Playback controls
368
+
369
+ ### Phase 3 (Advanced) 🚀
370
+ - [ ] Multi-session observer (watch multiple sessions)
371
+ - [ ] WebSocket real-time updates (no polling)
372
+ - [ ] Audio waveform visualization
373
+ - [ ] Beat search/filter
374
+ - [ ] Export timeline as video
375
+
376
+ ### Phase 4 (Immersive) 🌌
377
+ - [ ] 3D narrative space visualization
378
+ - [ ] VR/AR mode for narrative exploration
379
+ - [ ] Collaborative real-time viewing
380
+ - [ ] AI-generated beat summaries
381
+ - [ ] Narrative pattern recognition
382
+
383
+ ## Technical Considerations
384
+
385
+ ### Performance
386
+ - Polling every 2s is acceptable for local files
387
+ - Consider debouncing rapid consecutive changes
388
+ - Lazy-load audio files (stream, don't buffer all)
389
+
390
+ ### Audio Synchronization
391
+ - Queue management: FIFO for auto-play
392
+ - Handle concurrent audio generation
393
+ - Graceful degradation if audio missing
394
+
395
+ ### State Management
396
+ - Use React Context for live mode state
397
+ - Local storage for user preferences (volume, auto-play)
398
+ - Session storage for current playback position
399
+
400
+ ### Accessibility
401
+ - Keyboard controls for audio (space = play/pause)
402
+ - Screen reader announcements for new beats
403
+ - Visual indicators for audio playback state
404
+
405
+ ## Integration Points
406
+
407
+ ### With stc_observer.sh
408
+ - Observer writes to JSONL
409
+ - Generates timestamped audio files
410
+ - Visualizer reads both sources
411
+
412
+ ### With coaia-narrative MCP
413
+ - Shared JSONL format
414
+ - Compatible with existing tools
415
+ - Can edit via visualizer, view via MCP
416
+
417
+ ### With Session Management
418
+ - Multiple session directories
419
+ - Session selector in UI
420
+ - Parallel session monitoring
421
+
422
+ ## Error Handling
423
+
424
+ - JSONL parse errors → Show error banner, keep previous state
425
+ - Audio file not found → Silent skip, visual indicator
426
+ - Network/file system errors → Retry with exponential backoff
427
+ - Malformed beat data → Log, skip, continue
428
+
429
+ ## Success Metrics
430
+
431
+ - ✅ Zero manual reloads needed
432
+ - ✅ Audio plays within 3s of beat creation
433
+ - ✅ Smooth animations at 60fps
434
+ - ✅ No memory leaks during extended monitoring
435
+ - ✅ Intuitive UX for narrative flow comprehension