ib_conversations 0.0.6-alpha copy "ib_conversations: ^0.0.6-alpha" to clipboard
ib_conversations: ^0.0.6-alpha copied to clipboard

AI Conversations Flutter widget library

example/lib/main.dart

// example/lib/main.dart

import 'package:flutter/material.dart';
import 'package:provider/provider.dart'; // Required for Provider
import 'package:file_picker/file_picker.dart'; // Required for PlatformFile

// Import the entire ib_conversations library using its main export file
import 'package:ib_conversations/ib_conversations.dart' as ib_conversations;

// Instantiate and populate the registry once, globally for this example app.
// In a real application, you might manage the lifecycle of this registry
// based on your state management strategy.
final ib_conversations.EmbeddedWidgetRegistry globalWidgetRegistry = ib_conversations.EmbeddedWidgetRegistry();

void main() {
  // Register all embedded widget libraries during application startup.
  // This makes all defined embedded widgets available to the registry.
  ib_conversations.registerAllEmbeddedWidgets(globalWidgetRegistry);

  // Optional: Print metadata for debugging or backend reference
  print('--- Registered Widget Metadata (from example main.dart) ---');
  globalWidgetRegistry.getAllMetadata().forEach((label, metadata) {
    print('Label: $label');
    print('  Description: ${metadata.description}');
    print('  Parameters: ${metadata.parameterSchema}');
  });
  print('----------------------------------------------------------');


  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    // Provide the EmbeddedWidgetRegistry instance high up in the widget tree
    // using Provider. This allows ConversationMessage and its builders
    // to access the registry via Provider.of(context).
    return Provider<ib_conversations.EmbeddedWidgetRegistry>.value(
      value: globalWidgetRegistry, // Provide the instantiated registry
      child: MaterialApp(
        title: 'ib_conversations Example App',
        theme: ThemeData(
          primarySwatch: Colors.blue,
          visualDensity: VisualDensity.adaptivePlatformDensity,
        ),
        // The home page is a simple ChatPage that will use the ConversationWidget
        home: ChatPage(userId: 'example_user_123'),
      ),
    );
  }
}

/// A simple example page that hosts the ConversationWidget.
class ChatPage extends StatefulWidget {
  final String userId; // Example: User ID for the chat

  const ChatPage({Key? key, required this.userId}) : super(key: key);

  @override
  _ChatPageState createState() => _ChatPageState();
}

class _ChatPageState extends State<ChatPage> {

  // --- Implement the SendChatDataToBackend callback ---
  // This function sends the main chat input (text/files) to your backend AI service.
  // In a real application, this would contain your actual API call logic.
  // It receives PlatformFile list directly from the ConversationWidget.
  Future<String?> _sendChatDataToBackend({
    required String message,
    String? flavor,
    List<PlatformFile>? files, // Receive PlatformFile list
    Map<String, dynamic>? additionalArgs,
  }) async {
    print('Example App: Sending chat data to backend...');
    print('  Message: "$message"');
    print('  Flavor: "$flavor"');
    print('  Files attached: ${files?.length ?? 0}');
    if (files != null) {
      for (var file in files) {
        print('    - File: ${file.name} (${file.size} bytes)');
        // In a real app, you would handle file upload here (e.g., convert to bytes, send via API)
      }
    }
    print('  Additional Args: $additionalArgs');

    // TODO: Replace with your actual backend API call logic.
    // This is where you would use packages like 'http' or 'dio'
    // to send data to your Spring backend or call FlutterFlow generated APIs.
    // If using FF APIs, you would convert PlatformFile to FFUploadedFile here.

    // Example: Simulate a backend response after a delay
    await Future.delayed(Duration(seconds: 2));
    final simulatedResponse = "AI received: '$message'.\n\nHere are some options for you:\n```widget:action_button\n{\"label\": \"Tell Me More\", \"commandType\": \"tell_more\", \"args\": {\"topic\": \"response\"}}\n```\n```widget:questionnaire\n{\"questionText\": \"Was this helpful?\", \"selectionType\": \"single\", \"options\": [{\"value\": \"yes\", \"text\": \"Yes\"}, {\"value\": \"no\", \"text\": \"No\"}], \"commandType\": \"feedback\", \"questionId\": \"helpful_001\"}\n```\n```widget:data_display\n{\"text\": \"Current time is ${DateTime.now().toLocal().toShortString()}\"}\n```\n";
    print('Example App: Received simulated AI response.');
    return simulatedResponse; // Return the simulated AI response string
  }

  // --- Implement the SendCommandCallback for embedded widgets ---
  // This function handles commands triggered by embedded widgets within messages.
  // This is where you define what happens when an embedded widget is interacted with.
  void _handleEmbeddedWidgetCommand(String commandType, Map<String, dynamic> args) {
    print('Example App: Received command from embedded widget:');
    print('  Command Type: "$commandType"');
    print('  Arguments: $args');

    // TODO: Implement logic to interpret commands and trigger actions.
    // - Trigger a specific backend API call based on commandType and args.
    // - Update application state (using Provider, setState, etc.).
    // - Trigger navigation.
    // - Show a different widget in a side canvas/modal (if you implement that UI).

    // Example: Show a SnackBar based on the command
    ScaffoldMessenger.of(context).showSnackBar(
      SnackBar(
        content: Text('Command received: $commandType with args $args'),
        duration: Duration(seconds: 3),
      ),
    );

    // Example: If a command is meant to trigger a backend action based on embedded widget interaction
    // _sendEmbeddedWidgetCommandToBackend(commandType, args);
  }


  @override
  Widget build(BuildContext context) {
    // Access theme data from Flutter's Theme
    final theme = Theme.of(context);

    return Scaffold(
      appBar: AppBar(title: Text('AI Chat Example')),
      body: Column(
        children: [
          Expanded(
            // Use the ConversationWidget from your ib_conversations library
            child: ib_conversations.ConversationWidget(
              width: double.infinity, // Match parent width
              height: double.infinity, // Match parent height
              user: widget.userId,
              showTextInput: true, // Or pass from page parameters
              flavor: 'DEFAULT', // Or pass from page parameters

              // Pass theme parameters from the current Flutter Theme
              primaryColor: theme.primaryColor,
              primaryText: theme.textTheme.bodyMedium?.color, // Use null-safe access
              secondaryText: theme.textTheme.bodySmall?.color, // Use null-safe access
              alternateColor: theme.dividerColor, // Example mapping
              primaryBackground: theme.scaffoldBackgroundColor, // Example mapping
              secondaryBackground: theme.cardColor, // Example mapping
              errorColor: theme.colorScheme.error, // Example mapping
              bodyMediumStyle: theme.textTheme.bodyMedium,
              labelMediumStyle: theme.inputDecorationTheme.hintStyle, // Example mapping
              bodySmallStyle: theme.textTheme.bodySmall,

              // Pass the implemented backend interaction callbacks
              sendChatDataToBackend: _sendChatDataToBackend,
              handleEmbeddedWidgetCommand: _handleEmbeddedWidgetCommand,
            ),
          ),
          // Optional: Add a canvas/side panel area here, managed by _ChatPageState state
          // This area would display widgets triggered by 'show_canvas_widget' commands
          // if (_showCanvas)
          //   Container(
          //     width: 300, // Example width
          //     color: Colors.grey[200],
          //     child: Center(child: Text('Canvas Area')), // Replace with dynamic widget loading
          //     // You would dynamically build the widget here based on _canvasWidgetLabel and _canvasWidgetParams
          //     // using your registry:
          //     // final registry = Provider.of<ib_conversations.EmbeddedWidgetRegistry>(context);
          //     // final builder = registry.getBuilder(_canvasWidgetLabel!);
          //     // if (builder != null) builder(context, _canvasWidgetParams ?? {}, _handleEmbeddedWidgetCommand)
          //   ),
        ],
      ),
    );
  }
}

// Helper extension for DateTime to match the simulated data display widget
extension on DateTime {
  String toShortString() {
    return '${this.year}-${this.month.toString().padLeft(2, '0')}-${this.day.toString().padLeft(2, '0')} ${this.hour.toString().padLeft(2, '0')}:${this.minute.toString().padLeft(2, '0')}';
  }
}