chat_pilot_kit_flutter 1.0.0-beta.1
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 #
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_dartis 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