feat: Add transcript engine API and connect ConversationHistory to real data

- Add transcript-engine service that parses Claude Code JSONL transcripts
  with session listing, message extraction, token/stats analysis, and caching
- Add transcript REST routes (sessions list, latest, by session ID, section filtering)
- Rewrite ConversationHistory to fetch from /api/transcript/* instead of mock data
- Add session pills for switching between conversation sessions
- Add stats bar footer with model, duration, tokens, and tool count
- Add TranscriptSession/TranscriptMessage types, ChatInput, InputSettings,
  PromptBar updates, TranscriptCard, and useVoiceCapture composable
This commit is contained in:
2026-02-15 20:05:27 -06:00
parent 68edc01d44
commit f3ac7986ec
10 changed files with 2246 additions and 97 deletions

View File

@@ -0,0 +1,81 @@
import { jsonResponse, errorResponse } from '../utils/cors'
import { getTranscriptAnalysis, listSessions } from '../services/transcript-engine'
import type { TranscriptAnalysis } from '../services/transcript-engine'
export function handleTranscriptSessions(): Response {
const sessions = listSessions()
return jsonResponse(sessions)
}
export function handleTranscript(req: Request, url: URL, sessionId: string): Response {
if (req.method !== 'GET') return errorResponse('Method not allowed', 405)
const resolvedId = sessionId === 'latest' ? undefined : sessionId
const analysis = getTranscriptAnalysis(resolvedId)
if (!analysis) {
return errorResponse('Transcript not found', 404)
}
// Section filtering
const section = url.searchParams.get('section')
if (section) {
return handleSection(analysis, section)
}
// Full response (exclude thinking by default)
const includeThinking = url.searchParams.get('includeThinking') === 'true'
if (!includeThinking) {
return jsonResponse({
...analysis,
messages: analysis.messages.filter(m => !m.isMeta)
})
}
return jsonResponse({
...analysis,
messages: analysis.messages.filter(m => !m.isMeta)
})
}
function handleSection(analysis: TranscriptAnalysis, section: string): Response {
switch (section) {
case 'messages':
return jsonResponse({
sessionId: analysis.sessionId,
messages: analysis.messages.filter(m => !m.isMeta)
})
case 'tokens':
return jsonResponse({
sessionId: analysis.sessionId,
tokens: analysis.tokens
})
case 'tools':
return jsonResponse({
sessionId: analysis.sessionId,
tools: analysis.tools
})
case 'stats':
return jsonResponse({
sessionId: analysis.sessionId,
stats: analysis.stats,
model: analysis.model,
version: analysis.version,
duration: analysis.duration,
startTime: analysis.startTime,
endTime: analysis.endTime
})
case 'files':
return jsonResponse({
sessionId: analysis.sessionId,
filesModified: analysis.filesModified
})
case 'subagents':
return jsonResponse({
sessionId: analysis.sessionId,
subagents: analysis.subagents
})
default:
return errorResponse(`Unknown section: ${section}. Valid: messages, tokens, tools, stats, files, subagents`, 400)
}
}