| import 'dart:convert'; |
| |
| import 'package:schemantic/schemantic.dart'; |
| |
| import '../../../shared/logic/app_event_bus.dart'; |
| import '../../models/agent_tool_event.dart'; |
| import '../../models/agent_tool_id.dart'; |
| import '../base/agent_tool.dart'; |
| import '../tool_schemas.dart'; |
| |
| /// Simulates entering text into an input widget (TextField, etc.) by its nodeId. |
| class EnterTextTool extends AgentTool<EnterTextInput, EnterTextOutput> { |
| const EnterTextTool({ |
| required this.commandBus, |
| }); |
| |
| final AppCommandBus commandBus; |
| |
| @override |
| AgentToolId get toolId => AgentToolId.enterText; |
| |
| @override |
| String get description => |
| 'Enter text into a text input widget (like TextField) in the running app preview using its node ID. ' |
| 'The widget must be or contain an editable text field.'; |
| |
| @override |
| SchemanticType<EnterTextInput> get inputSchema => EnterTextInput.$schema; |
| |
| @override |
| SchemanticType<EnterTextOutput> get outputSchema => EnterTextOutput.$schema; |
| |
| Future<String?> _invokeExtension(String method, Map<String, String> args) { |
| return commandBus.dispatchAsync(InvokePreviewExtensionCommand( |
| method: method, |
| args: args, |
| )); |
| } |
| |
| @override |
| Future<EnterTextOutput> run(EnterTextInput input) async { |
| final state = await commandBus.dispatchAsync(RequestPreviewStateCommand()); |
| if (!state.hasActiveRuntime) { |
| return EnterTextOutput( |
| success: false, |
| error: 'No app preview is running. Use controlApp to start the preview first.', |
| ); |
| } |
| |
| try { |
| final responseStr = await _invokeExtension( |
| 'ext.vibepad.enterText', |
| { |
| 'id': input.nodeId, |
| 'text': input.text, |
| }, |
| ); |
| |
| if (responseStr == null || responseStr.isEmpty) { |
| return EnterTextOutput( |
| success: false, |
| error: 'Could not enter text. No response from the running app.', |
| ); |
| } |
| |
| final Map<String, dynamic> decoded = jsonDecode(responseStr) as Map<String, dynamic>; |
| final success = decoded['success'] as bool? ?? false; |
| if (!success) { |
| return EnterTextOutput( |
| success: false, |
| error: decoded['error'] as String? ?? 'Failed to enter text.', |
| ); |
| } |
| |
| return EnterTextOutput( |
| success: true, |
| ); |
| } catch (e) { |
| return EnterTextOutput( |
| success: false, |
| error: 'Failed to enter text into widget "${input.nodeId}": $e', |
| ); |
| } |
| } |
| |
| String _truncate(String val, int maxLength) { |
| if (val.length <= maxLength) { |
| return val; |
| } |
| return '${val.substring(0, maxLength)}...'; |
| } |
| |
| @override |
| AgentToolEvent mapStartToToolEvent(EnterTextInput input) { |
| final txt = _truncate(input.text, 20); |
| return createToolEvent( |
| status: ToolStatus.running, |
| summary: 'Entering "$txt" into widget "${input.nodeId}"', |
| ); |
| } |
| |
| @override |
| AgentToolEvent mapToToolEvent( |
| EnterTextInput input, |
| EnterTextOutput output, |
| ) { |
| final txt = _truncate(input.text, 20); |
| return createToolEvent( |
| status: output.success ? ToolStatus.success : ToolStatus.error, |
| summary: output.success |
| ? 'Entered "$txt" into widget "${input.nodeId}"' |
| : 'Failed to enter text into widget "${input.nodeId}"', |
| errorMessage: output.error, |
| ); |
| } |
| } |