find_ai_chat 0.4.0
find_ai_chat: ^0.4.0 copied to clipboard
Flutter SDK for the Find AI chat assistant. Drop-in widget with floating, drawer and embedded modes, SSE streaming, and signed-mode visitor auth.
example/lib/main.dart
import 'package:find_ai_chat/find_ai_chat.dart';
import 'package:flutter/material.dart';
const String kExampleConnectionId = 'dca2bd9ae5034784b61db61f53c7bbcb';
const String kExampleBaseUrl = 'http://localhost:8000';
void main() {
FindChatEnvironment.configure(baseUrl: kExampleBaseUrl);
runApp(const ExampleApp());
}
class ExampleApp extends StatelessWidget {
const ExampleApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'find_ai_chat example',
debugShowCheckedModeBanner: false,
theme: ThemeData(
colorSchemeSeed: const Color(0xFF635BFF),
useMaterial3: true,
fontFamily: 'Inter',
scaffoldBackgroundColor: const Color(0xFFF6F8FA),
),
home: const HomeScreen(),
);
}
}
class HomeScreen extends StatefulWidget {
const HomeScreen({super.key});
@override
State<HomeScreen> createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
FindChatMode _mode = FindChatMode.floating;
FindChatTheme? _theme;
FindChatVisualStyle? _visualStyle;
late final FindChatController _controller = FindChatController(
connectionId: kExampleConnectionId,
);
@override
void dispose() {
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
child: Column(
children: [
_buildHeader(),
Expanded(
child: _mode == FindChatMode.embedded
? _buildEmbeddedChat()
: _buildPlayground(),
),
],
),
),
floatingActionButton: _mode == FindChatMode.embedded
? null
: FindChatWidget(
connectionId: kExampleConnectionId,
controller: _controller,
mode: _mode,
theme: _theme,
visualStyle: _visualStyle,
),
);
}
Widget _buildHeader() {
return Container(
padding: const EdgeInsets.fromLTRB(24, 20, 24, 16),
decoration: const BoxDecoration(
color: Colors.white,
border: Border(bottom: BorderSide(color: Color(0xFFE3E8EF))),
),
child: Row(
children: [
Container(
width: 32,
height: 32,
decoration: BoxDecoration(
gradient: const LinearGradient(
colors: [Color(0xFF635BFF), Color(0xFF8B5CF6)],
),
borderRadius: BorderRadius.circular(8),
),
padding: const EdgeInsets.all(4),
child: Image.asset('assets/find_logo.png', fit: BoxFit.contain),
),
const SizedBox(width: 12),
const Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'find_ai_chat',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.w600,
color: Color(0xFF0A2540),
letterSpacing: -0.3,
),
),
Text(
'Interactive SDK playground',
style: TextStyle(
fontSize: 12,
color: Color(0xFF596780),
),
),
],
),
const Spacer(),
_buildPill('v0.1.0'),
],
),
);
}
Widget _buildPill(String label) {
return Container(
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4),
decoration: BoxDecoration(
color: const Color(0xFFF0EEFF),
borderRadius: BorderRadius.circular(20),
),
child: Text(
label,
style: const TextStyle(
fontSize: 11,
fontWeight: FontWeight.w500,
color: Color(0xFF635BFF),
),
),
);
}
Widget _buildPlayground() {
return SingleChildScrollView(
padding: const EdgeInsets.all(24),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildSection(
title: 'Display Mode',
child: _buildChipGroup<FindChatMode>(
options: {
FindChatMode.floating: ('Floating', Icons.bubble_chart_outlined),
FindChatMode.drawer: ('Drawer', Icons.menu_open_rounded),
FindChatMode.embedded: ('Embedded', Icons.dashboard_outlined),
},
selected: _mode,
onSelected: (v) => setState(() => _mode = v),
),
),
const SizedBox(height: 20),
_buildSection(
title: 'Theme',
child: _buildChipGroup<FindChatTheme?>(
options: {
null: ('From config', Icons.settings_outlined),
FindChatTheme.light: ('Light', Icons.light_mode_outlined),
FindChatTheme.dark: ('Dark', Icons.dark_mode_outlined),
},
selected: _theme,
onSelected: (v) => setState(() => _theme = v),
),
),
const SizedBox(height: 20),
_buildSection(
title: 'Visual Style',
child: _buildChipGroup<FindChatVisualStyle?>(
options: {
null: ('From config', Icons.settings_outlined),
FindChatVisualStyle.classic: ('Classic', Icons.chat_outlined),
FindChatVisualStyle.gptStyle: ('GPT style', Icons.auto_awesome_outlined),
},
selected: _visualStyle,
onSelected: (v) => setState(() => _visualStyle = v),
),
),
const SizedBox(height: 28),
_buildSection(
title: 'Programmatic Control',
subtitle: 'Call these methods from anywhere in your app',
child: _buildControlButtons(),
),
const SizedBox(height: 28),
_buildHintCard(),
],
),
);
}
Widget _buildSection({
required String title,
String? subtitle,
required Widget child,
}) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
title.toUpperCase(),
style: const TextStyle(
fontSize: 11,
fontWeight: FontWeight.w600,
color: Color(0xFF596780),
letterSpacing: 0.8,
),
),
if (subtitle != null) ...[
const SizedBox(height: 2),
Text(
subtitle,
style: const TextStyle(fontSize: 12, color: Color(0xFF8792A2)),
),
],
const SizedBox(height: 10),
child,
],
);
}
Widget _buildChipGroup<T>({
required Map<T, (String, IconData)> options,
required T selected,
required ValueChanged<T> onSelected,
}) {
return Wrap(
spacing: 8,
runSpacing: 8,
children: options.entries.map((e) {
final isActive = e.key == selected;
return GestureDetector(
onTap: () => onSelected(e.key),
child: AnimatedContainer(
duration: const Duration(milliseconds: 180),
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 10),
decoration: BoxDecoration(
color: isActive ? const Color(0xFF635BFF) : Colors.white,
borderRadius: BorderRadius.circular(10),
border: Border.all(
color: isActive
? const Color(0xFF635BFF)
: const Color(0xFFE3E8EF),
),
boxShadow: isActive
? [
BoxShadow(
color: const Color(0xFF635BFF).withValues(alpha: 0.25),
blurRadius: 8,
offset: const Offset(0, 2),
),
]
: [
BoxShadow(
color: Colors.black.withValues(alpha: 0.04),
blurRadius: 4,
offset: const Offset(0, 1),
),
],
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
e.value.$2,
size: 16,
color: isActive ? Colors.white : const Color(0xFF596780),
),
const SizedBox(width: 6),
Text(
e.value.$1,
style: TextStyle(
fontSize: 13,
fontWeight: FontWeight.w500,
color: isActive ? Colors.white : const Color(0xFF0A2540),
),
),
],
),
),
);
}).toList(),
);
}
Widget _buildControlButtons() {
return Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(12),
border: Border.all(color: const Color(0xFFE3E8EF)),
),
child: Wrap(
spacing: 8,
runSpacing: 8,
children: [
_buildActionBtn('open()', Icons.open_in_new_rounded, _controller.open),
_buildActionBtn('close()', Icons.close_rounded, _controller.close),
_buildActionBtn('toggle()', Icons.swap_vert_rounded, _controller.toggle),
_buildActionBtn(
'sendMessage()',
Icons.send_rounded,
() => _controller.sendMessage('Hello from the example app'),
outlined: true,
),
_buildActionBtn(
'clearConversation()',
Icons.delete_outline_rounded,
_controller.clearConversation,
outlined: true,
),
],
),
);
}
Widget _buildActionBtn(
String label,
IconData icon,
VoidCallback onPressed, {
bool outlined = false,
}) {
return Material(
color: outlined ? Colors.transparent : const Color(0xFF0A2540),
borderRadius: BorderRadius.circular(8),
child: InkWell(
onTap: onPressed,
borderRadius: BorderRadius.circular(8),
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
decoration: outlined
? BoxDecoration(
borderRadius: BorderRadius.circular(8),
border: Border.all(color: const Color(0xFFE3E8EF)),
)
: null,
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(
icon,
size: 14,
color: outlined ? const Color(0xFF596780) : Colors.white,
),
const SizedBox(width: 6),
Text(
label,
style: TextStyle(
fontSize: 12,
fontWeight: FontWeight.w500,
fontFamily: 'monospace',
color: outlined ? const Color(0xFF0A2540) : Colors.white,
),
),
],
),
),
),
);
}
Widget _buildHintCard() {
final hint = _mode == FindChatMode.floating
? 'Tap the floating bubble in the bottom-right corner to open the chat.'
: _mode == FindChatMode.drawer
? 'Use the open() button above or controller.open() to reveal the drawer panel.'
: 'Switch to Embedded mode to see the chat inline below.';
return Container(
padding: const EdgeInsets.all(14),
decoration: BoxDecoration(
color: const Color(0xFFFFF8E1),
borderRadius: BorderRadius.circular(10),
border: Border.all(color: const Color(0xFFFFE082)),
),
child: Row(
children: [
const Icon(Icons.lightbulb_outline_rounded, size: 16, color: Color(0xFFF9A825)),
const SizedBox(width: 10),
Expanded(
child: Text(
hint,
style: const TextStyle(fontSize: 12, color: Color(0xFF5D4037)),
),
),
],
),
);
}
Widget _buildEmbeddedChat() {
return Padding(
padding: const EdgeInsets.all(24),
child: Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(16),
border: Border.all(color: const Color(0xFFE3E8EF)),
boxShadow: [
BoxShadow(
color: Colors.black.withValues(alpha: 0.06),
blurRadius: 16,
offset: const Offset(0, 4),
),
],
),
child: ClipRRect(
borderRadius: BorderRadius.circular(16),
child: FindChatWidget(
connectionId: kExampleConnectionId,
controller: _controller,
mode: FindChatMode.embedded,
theme: _theme,
visualStyle: _visualStyle,
),
),
),
);
}
}