chat_pilot_kit_flutter 1.0.0-beta.1 copy "chat_pilot_kit_flutter: ^1.0.0-beta.1" to clipboard
chat_pilot_kit_flutter: ^1.0.0-beta.1 copied to clipboard

Flutter widgets and Riverpod integration for chat_pilot_kit_dart.

chat_pilot_kit_flutter #

pub package License: MIT

English | 简体中文

Flutter widgets and Riverpod integration for chat_pilot_kit_dart.

Provides ready-made conversation UI, 8 built-in node views, theming, streaming markdown rendering, and a NodeView extension system.

Install #

dependencies:
  chat_pilot_kit_flutter: ^1.0.0-beta.1

chat_pilot_kit_dart is re-exported automatically — no need to add it separately.

Quick Start #

import 'package:flutter/material.dart';
import 'package:chat_pilot_kit_flutter/chat_pilot_kit_flutter.dart';

void main() {
  final extensions = getDefaultFlutterExtensions();
  final controller = createChatPilotKit(
    agentService: MyAgentService(),
    overrideExtensions: extensions,
  );

  runApp(
    ChatPilotKitScope(
      controller: controller,
      child: const MyApp(),
    ),
  );
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        body: ConversationListView(
          extensions: extensions,
          padding: const EdgeInsets.all(12),
        ),
      ),
    );
  }
}

ChatPilotKitScope provides the controller to all descendants via Riverpod. ConversationListView renders all conversations with built-in node views.

Providers #

All providers are automatically available inside ChatPilotKitScope:

Provider Type Description
chatPilotKitControllerProvider Provider<ChatPilotKitController> The main controller instance
conversationsProvider StreamProvider<List<ConversationBean>> Reactive conversation list
isQueryingProvider StreamProvider<bool> Whether a query is in progress
nodeInteractionProvider StreamProvider<NodeInteractionEvent> Global node interaction stream
filteredNodeInteractionProvider StreamProvider.family Filtered node interaction stream
class ChatPage extends ConsumerWidget {
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final controller = ref.watch(chatPilotKitControllerProvider);
    final isQuerying = ref.watch(isQueryingProvider).value ?? false;

    return Column(
      children: [
        Expanded(child: ConversationListView()),
        ElevatedButton(
          onPressed: isQuerying
              ? controller.interrupt
              : () => controller.query('Hello'),
          child: Text(isQuerying ? 'Stop' : 'Send'),
        ),
      ],
    );
  }
}

Built-in Node Views #

Node Type Widget Streamable Customization
text TextNodeView No textBuilder
markdown MarkdownNodeView Yes codeBlockBuilder, customBuilders
image ImageNodeView No imageBuilder, captionBuilder
audio AudioNodeView No audioPlayerBuilder
video VideoNodeView No videoPlayerBuilder
file FileNodeView No iconBuilder, downloadIconBuilder
thinking ThinkingBlockNodeView Yes iconBuilder, labelBuilder, contentBuilder
tool_call ToolCallNodeView No iconBuilder, badgeBuilder

NodeView Extension System #

1. customViewBuilders — Register a new node type #

ConversationListView(
  extensions: extensions,
  customViewBuilders: {
    'chart': (props) => ChartWidget(
      data: (props.node as GenericNode).content,
    ),
  },
)

2. flutterNodeView — Override a built-in NodeView #

Keep the extension's processing/streaming logic, replace only the UI:

final styledText = flutterNodeView(
  TextExtension(),
  (props) => MyStyledTextView(props: props),
);

final controller = createChatPilotKit(
  agentService: service,
  overrideExtensions: [styledText, ...otherExtensions],
);

3. getDefaultFlutterExtensions — Pre-bound extension set #

// Store once — reuse for both controller and ConversationListView
final extensions = getDefaultFlutterExtensions();

final controller = createChatPilotKit(
  agentService: service,
  overrideExtensions: extensions,
);

// Pass to ConversationListView for Extension-level NodeView dispatch
ConversationListView(extensions: extensions)

NodeViewProps #

Every node view receives NodeViewProps:

Field Type Description
node ConversationNode The node instance
conversation ConversationBean Parent conversation
role ConversationRole .client or .aiWorker
completed bool Whether the node is done streaming
updateContent Function(Object)? Update node content
updateMetadata Function(Map)? Update node metadata
emitInteraction Function(String, {data})? Emit interaction event
destroy Function()? Dispose the node
onCommand StreamSubscription Function(handler)? Subscribe to host commands

Node Interaction #

// Listen to interactions from NodeViews
ref.listen(nodeInteractionProvider, (prev, next) {
  final event = next.value;
  if (event != null) {
    print('[${event.type}] nodeId=${event.nodeId} data=${event.data}');
  }
});

// Filter by node type
final toolCallSub = ref.watch(
  filteredNodeInteractionProvider(
    (event) => event.nodeType == 'tool_call',
  ),
);

// Send a command to a specific node
sendNodeCommand(controller, nodeId: 'node-123', type: 'highlight');

Theming #

Node views use ChatPilotKitTheme for consistent styling. By default, colors are derived from the surrounding ThemeData.

ChatPilotKitThemeScope(
  theme: ChatPilotKitTheme(
    bubbleAiBackground: Colors.grey.shade100,
    bubbleUserBackground: Colors.blue,
    bubbleAiForeground: Colors.black87,
    bubbleUserForeground: Colors.white,
    accent: Colors.blue,
    codeBackground: const Color(0xFFF6F8FA),
    codeForeground: const Color(0xFF1F2328),
    inlineCodeBackground: const Color(0x1F818B98),
    borderColor: const Color(0xFFD1D9E0),
    mutedForeground: const Color(0xFF59636E),
    bubbleRadius: 16,
  ),
  child: ConversationListView(),
)

Access in custom widgets:

final cpkTheme = ChatPilotKitThemeScope.of(context);

Streaming #

Streamable nodes (markdown, thinking) automatically update in real-time via StreamBuilder on node.onContentUpdate. No extra setup needed.

Attachments #

await controller.queryWithAttachments(
  'Check this file',
  [
    AttachmentInput(
      url: 'file:///path/to/doc.pdf',
      fileName: 'doc.pdf',
      fileSize: 2048000,
      fileType: 'application/pdf',
    ),
  ],
);

Conversation Persistence #

// Export
final snapshots = controller.exportConversations();
final json = snapshots.map((s) => s.toJson()).toList();

// Import (prepend, default)
controller.importConversations(inputs);

// Import (replace all)
controller.importConversations(
  inputs,
  options: const ImportOptions(position: ImportPosition.replace),
);

Full Example #

See the example app for a complete playground with:

  • Chat tab with text input and attachment support
  • Demos tab with behavior / target / media / NodeView extension demos
  • Settings tab with theme switching and NodeView gallery
  • NodeView Extension page showing all 3 extension patterns
cd example && flutter run -d <device>

API Reference #

ChatPilotKitScope #

ChatPilotKitScope(
  controller: controller,  // required
  child: MyApp(),
)

ConversationListView #

ConversationListView(
  extensions: extensions,           // List<MessageExtensionInstance>?
  customViewBuilders: {...},        // Map<String, NodeViewBuilder>?
  autoScroll: true,
  padding: EdgeInsets.all(16),
  separatorBuilder: (ctx, i) => Divider(),
)

NodeRenderer #

NodeRenderer(
  node: node,
  conversation: bean,
  extensions: extensions,           // Extension-level dispatch
  customViewBuilders: {...},        // Highest priority
)

Publishing #

yarn dart:publish:dry   # dry run
yarn dart:publish       # publish to pub.dev

License #

MIT