Initial commit: Claude Code session viewer (Flutter macOS)
A desktop app that parses Claude Code .jsonl session logs and provides a rich UI for exploring conversations, tool usage, subagents, and token consumption. Features include project browser with auto-discovery of ~/.claude/projects, conversation timeline with inline subagent expansion, agents overview, toolbelt chart, and token usage dashboard. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,116 @@
|
||||
class ContentBlock {
|
||||
final String type;
|
||||
final Map<String, dynamic> raw;
|
||||
|
||||
ContentBlock({required this.type, required this.raw});
|
||||
|
||||
factory ContentBlock.fromJson(Map<String, dynamic> json) {
|
||||
final type = json['type'] as String? ?? 'unknown';
|
||||
switch (type) {
|
||||
case 'text':
|
||||
return TextBlock.fromJson(json);
|
||||
case 'thinking':
|
||||
return ThinkingBlock.fromJson(json);
|
||||
case 'tool_use':
|
||||
return ToolUseBlock.fromJson(json);
|
||||
default:
|
||||
return ContentBlock(type: type, raw: json);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class TextBlock extends ContentBlock {
|
||||
final String text;
|
||||
|
||||
TextBlock({required this.text, required Map<String, dynamic> raw})
|
||||
: super(type: 'text', raw: raw);
|
||||
|
||||
factory TextBlock.fromJson(Map<String, dynamic> json) {
|
||||
return TextBlock(
|
||||
text: json['text'] as String? ?? '',
|
||||
raw: json,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class ThinkingBlock extends ContentBlock {
|
||||
final String thinking;
|
||||
final String? signature;
|
||||
|
||||
ThinkingBlock({
|
||||
required this.thinking,
|
||||
this.signature,
|
||||
required Map<String, dynamic> raw,
|
||||
}) : super(type: 'thinking', raw: raw);
|
||||
|
||||
factory ThinkingBlock.fromJson(Map<String, dynamic> json) {
|
||||
return ThinkingBlock(
|
||||
thinking: json['thinking'] as String? ?? '',
|
||||
signature: json['signature'] as String?,
|
||||
raw: json,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class ToolUseBlock extends ContentBlock {
|
||||
final String id;
|
||||
final String name;
|
||||
final Map<String, dynamic> input;
|
||||
ToolResultData? linkedResult;
|
||||
|
||||
ToolUseBlock({
|
||||
required this.id,
|
||||
required this.name,
|
||||
required this.input,
|
||||
this.linkedResult,
|
||||
required Map<String, dynamic> raw,
|
||||
}) : super(type: 'tool_use', raw: raw);
|
||||
|
||||
factory ToolUseBlock.fromJson(Map<String, dynamic> json) {
|
||||
return ToolUseBlock(
|
||||
id: json['id'] as String? ?? '',
|
||||
name: json['name'] as String? ?? '',
|
||||
input: (json['input'] as Map<String, dynamic>?) ?? {},
|
||||
raw: json,
|
||||
);
|
||||
}
|
||||
|
||||
bool get isAgentCall => name == 'Agent';
|
||||
String? get subagentType => isAgentCall ? input['subagent_type'] as String? : null;
|
||||
String? get agentDescription => isAgentCall ? input['description'] as String? : null;
|
||||
String? get agentPrompt => isAgentCall ? input['prompt'] as String? : null;
|
||||
}
|
||||
|
||||
class ToolResultData {
|
||||
final String toolUseId;
|
||||
final dynamic content;
|
||||
final bool isError;
|
||||
final Map<String, dynamic> raw;
|
||||
|
||||
ToolResultData({
|
||||
required this.toolUseId,
|
||||
this.content,
|
||||
this.isError = false,
|
||||
required this.raw,
|
||||
});
|
||||
|
||||
String get textContent {
|
||||
if (content is String) return content;
|
||||
if (content is List) {
|
||||
return (content as List)
|
||||
.where((c) => c is Map && c['type'] == 'text')
|
||||
.map((c) => c['text'] as String? ?? '')
|
||||
.join('\n');
|
||||
}
|
||||
return content?.toString() ?? '';
|
||||
}
|
||||
|
||||
factory ToolResultData.fromJson(Map<String, dynamic> json) {
|
||||
return ToolResultData(
|
||||
toolUseId: json['tool_use_id'] as String? ?? '',
|
||||
content: json['content'],
|
||||
isError: json['is_error'] as bool? ?? false,
|
||||
raw: json,
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user