Files
captain-claude/apps/captain-mobile-v2/flutter/lib/widgets/tool_use_card.dart
ARCHITECT f199daf4ba Change PIN to 1451
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-17 23:31:52 +00:00

166 lines
5.0 KiB
Dart

import 'dart:convert';
import 'package:flutter/material.dart';
import '../models/message.dart';
class ToolUseCard extends StatefulWidget {
final ToolUse toolUse;
const ToolUseCard({super.key, required this.toolUse});
@override
State<ToolUseCard> createState() => _ToolUseCardState();
}
class _ToolUseCardState extends State<ToolUseCard> {
bool _isExpanded = false;
IconData get _toolIcon {
switch (widget.toolUse.tool.toLowerCase()) {
case 'bash':
return Icons.terminal;
case 'read':
return Icons.description;
case 'write':
case 'edit':
return Icons.edit_document;
case 'grep':
case 'glob':
return Icons.search;
default:
return Icons.build;
}
}
String get _inputDisplay {
final input = widget.toolUse.input;
if (input == null) return '';
if (input is String) return input;
if (input is Map) {
// For Bash tool, show command
if (input.containsKey('command')) {
return input['command'];
}
// For Read tool, show file path
if (input.containsKey('file_path')) {
return input['file_path'];
}
// Otherwise show JSON
try {
return const JsonEncoder.withIndent(' ').convert(input);
} catch (_) {
return input.toString();
}
}
return input.toString();
}
@override
Widget build(BuildContext context) {
final hasOutput = widget.toolUse.output != null &&
widget.toolUse.output!.isNotEmpty;
return Container(
margin: const EdgeInsets.symmetric(vertical: 4),
decoration: BoxDecoration(
color: const Color(0xFF2D2D2D),
borderRadius: BorderRadius.circular(8),
border: Border.all(color: Colors.orange.shade700.withOpacity(0.3)),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
// Header
InkWell(
onTap: hasOutput
? () => setState(() => _isExpanded = !_isExpanded)
: null,
child: Padding(
padding: const EdgeInsets.all(12),
child: Row(
children: [
Icon(_toolIcon, size: 18, color: Colors.orange.shade400),
const SizedBox(width: 8),
Text(
widget.toolUse.tool,
style: TextStyle(
color: Colors.orange.shade400,
fontWeight: FontWeight.bold,
fontSize: 13,
),
),
const Spacer(),
if (hasOutput)
Icon(
_isExpanded ? Icons.expand_less : Icons.expand_more,
color: Colors.grey.shade500,
size: 20,
),
if (!hasOutput && widget.toolUse.output == null)
SizedBox(
width: 14,
height: 14,
child: CircularProgressIndicator(
strokeWidth: 2,
color: Colors.orange.shade400,
),
),
],
),
),
),
// Input/Command
if (_inputDisplay.isNotEmpty)
Container(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
decoration: BoxDecoration(
color: Colors.black.withOpacity(0.2),
),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'',
style: TextStyle(
color: Colors.green.shade400,
fontFamily: 'JetBrainsMono',
fontSize: 12,
),
),
const SizedBox(width: 8),
Expanded(
child: Text(
_inputDisplay,
style: const TextStyle(
color: Colors.white70,
fontFamily: 'JetBrainsMono',
fontSize: 12,
),
maxLines: 3,
overflow: TextOverflow.ellipsis,
),
),
],
),
),
// Output (collapsible)
if (_isExpanded && hasOutput)
Container(
constraints: const BoxConstraints(maxHeight: 200),
padding: const EdgeInsets.all(12),
child: SingleChildScrollView(
child: Text(
widget.toolUse.output!,
style: const TextStyle(
color: Colors.white60,
fontFamily: 'JetBrainsMono',
fontSize: 11,
),
),
),
),
],
),
);
}
}