简介:本文详细介绍如何使用Flutter3构建类似Deepseek/ChatGPT的流式聊天界面,并对接deepseek-chat API实现实时消息推送。涵盖界面设计、API对接、流式响应处理等核心环节,提供完整代码示例与优化方案。
在AI聊天应用开发中,流式响应(Streaming Response)技术能显著提升用户体验,实现类似Deepseek/ChatGPT的实时交互效果。Flutter3凭借其跨平台特性与高性能渲染引擎,成为构建此类应用的理想选择。本文将围绕以下核心目标展开:
技术栈选择:
class ChatScreen extends ConsumerWidget {@overrideWidget build(BuildContext context, WidgetRef ref) {final messages = ref.watch(chatMessagesProvider);final isLoading = ref.watch(isStreamingProvider);return Scaffold(appBar: CustomAppBar(),body: Column(children: [Expanded(child: MessageList(messages: messages),),if (isLoading) const TypingIndicator(),InputBar(),],),);}}
使用AnimatedList实现消息插入动画:
class MessageList extends StatelessWidget {final List<ChatMessage> messages;@overrideWidget build(BuildContext context) {return AnimatedList(key: GlobalKey<AnimatedListState>(),initialItemCount: messages.length,itemBuilder: (context, index, animation) {final message = messages[index];return SizeTransition(sizeFactor: animation,child: MessageBubble(message: message),);},);}}
通过CustomPaint与AnimationController模拟逐字显示:
class TypingIndicator extends StatefulWidget {@override_TypingIndicatorState createState() => _TypingIndicatorState();}class _TypingIndicatorState extends State<TypingIndicator>with SingleTickerProviderStateMixin {late AnimationController _controller;late Animation<double> _animation;@overridevoid initState() {super.initState();_controller = AnimationController(duration: const Duration(milliseconds: 1500),vsync: this,)..repeat();_animation = Tween<double>(begin: 0.3, end: 1.0).animate(_controller);}@overrideWidget build(BuildContext context) {return FadeTransition(opacity: _animation,child: Row(mainAxisSize: MainAxisSize.min,children: [const CircleAvatar(radius: 4, backgroundColor: Colors.grey),const SizedBox(width: 4),const CircleAvatar(radius: 4, backgroundColor: Colors.grey),const SizedBox(width: 4),const CircleAvatar(radius: 4, backgroundColor: Colors.grey),],),);}}
class DeepseekChatAPI {static final Dio _dio = Dio(BaseOptions(baseUrl: 'https://api.deepseek.com/v1',connectTimeout: const Duration(seconds: 10),receiveTimeout: const Duration(seconds: 30),),);static Future<Stream<String>> streamChatCompletion({required String apiKey,required String prompt,}) async {final response = await _dio.post('/chat/completions',data: {'model': 'deepseek-chat','messages': [{'role': 'user', 'content': prompt}],'stream': true,},options: Options(headers: {'Authorization': 'Bearer $apiKey'},),);return response.data.stream.transform(StreamTransformer.fromHandlers(handleData: (dynamic data, EventSink<String> sink) {final chunk = data['choices'][0]['delta']['content'] ?? '';if (chunk.isNotEmpty) sink.add(chunk);},),);}}
使用Riverpod管理流式状态:
final chatMessagesProvider = StateNotifierProvider<ChatMessagesNotifier,List<ChatMessage>>((ref) => ChatMessagesNotifier());final isStreamingProvider = StateProvider<bool>((ref) => false);class ChatMessagesNotifier extends StateNotifier<List<ChatMessage>> {ChatMessagesNotifier() : super([]);void addUserMessage(String content) {state = [...state, ChatMessage(content: content, isUser: true)];}void addBotMessageChunk(String chunk) {if (state.isNotEmpty && !state.last.isUser) {final lastMessage = state.last;state = [...state.sublist(0, state.length - 1),lastMessage.copyWith(content: lastMessage.content + chunk),];} else {state = [...state, ChatMessage(content: chunk, isUser: false)];}}}
Future<void> sendMessage(String prompt, String apiKey) async {final ref = ProviderScope.containerOf(context);ref.read(chatMessagesProvider.notifier).addUserMessage(prompt);ref.read(isStreamingProvider.notifier).state = true;try {await DeepseekChatAPI.streamChatCompletion(apiKey: apiKey,prompt: prompt,).listen((chunk) {ref.read(chatMessagesProvider.notifier).addBotMessageChunk(chunk);},onDone: () => ref.read(isStreamingProvider.notifier).state = false,onError: (e) {ref.read(isStreamingProvider.notifier).state = false;// 错误处理逻辑},);} catch (e) {ref.read(isStreamingProvider.notifier).state = false;// 异常处理}}
使用Equatable减少不必要的Widget重建
class ChatMessage extends Equatable {final String content;final bool isUser;final DateTime timestamp;@overrideList<Object?> get props => [content, isUser, timestamp];}
实现消息分页加载
```dart
class PagedMessageList extends StatefulWidget {
@override
_PagedMessageListState createState() => _PagedMessageListState();
}
class _PagedMessageListState extends State
final ScrollController _controller = ScrollController();
int _currentPage = 1;
bool _isLoading = false;
void _loadMore() async {
if (!_isLoading) {
_isLoading = true;
// 加载更多消息逻辑
_isLoading = false;
}
}
@override
Widget build(BuildContext context) {
return NotificationListener
onNotification: (notification) {
if (notification is ScrollEndNotification &&
_controller.position.extentAfter < 500) {
_loadMore();
}
return false;
},
child: ListView.builder(…),
);
}
}
### 4.2 网络异常处理```dartclass ErrorInterceptor extends Interceptor {@overridevoid onError(DioError err, ErrorInterceptorHandler handler) {if (err.type == DioErrorType.connectTimeout) {handler.resolve(Response(requestOptions: err.requestOptions,statusCode: 504,data: {'error': 'Connection timeout'},));} else {handler.next(err);}}}// 初始化Dio时添加拦截器_dio.interceptors.add(ErrorInterceptor());
单元测试:验证状态管理逻辑
test('addBotMessageChunk updates message correctly', () {final notifier = ChatMessagesNotifier();notifier.addUserMessage('Hello');notifier.addBotMessageChunk('World');expect(notifier.state.last.content, equals('World'));notifier.addBotMessageChunk('!');expect(notifier.state.last.content, equals('World!'));});
集成测试:模拟API响应
void main() {testWidgets('Stream response test', (WidgetTester tester) async {// 模拟Stream响应const testChunk = 'Test response';final stream = Stream.fromIterable([testChunk]);// 使用MockDio模拟API调用when(() => mockDio.post(any(), data: any(named: 'data'))).thenAnswer((_) async => Response(requestOptions: RequestOptions(path: ''),data: Stream.fromIterable([testChunk]),));await tester.pumpWidget(MaterialApp(home: ChatScreen()));// 验证UI更新逻辑});}
// 使用flutter_dotenv加载
final dotenv = DotEnv();
await dotenv.load(fileName: ‘.env’);
final apiKey = dotenv.env[‘DEEPSEEK_API_KEY’];
2. **平台适配**:- iOS需在Info.plist中添加:```xml<key>NSAppTransportSecurity</key><dict><key>NSAllowsArbitraryLoads</key><true/></dict>
<uses-permission android:name="android.permission.INTERNET" />
class ConversationManager {final List<Map<String, dynamic>> _history = [];void addToHistory(String role, String content) {_history.add({'role': role, 'content': content});// 限制历史记录长度if (_history.length > 10) {_history.removeAt(0);}}List<Map<String, dynamic>> getHistory() {return [..._history];}}
enum AIModel { deepseek, gpt35, gpt4 }class ModelConfig {static final Map<AIModel, String> endpoints = {AIModel.deepseek: 'https://api.deepseek.com/v1',AIModel.gpt35: 'https://api.openai.com/v1',};static final Map<AIModel, String> defaultModels = {AIModel.deepseek: 'deepseek-chat',AIModel.gpt35: 'gpt-3.5-turbo',};}
状态管理原则:
StateNotifier处理复杂状态API对接要点:
性能优化建议:
const构造函数减少Widget重建Isolate处理CPU密集型任务通过以上实现方案,开发者可以快速构建出具备流式响应能力的AI聊天界面,同时保证代码的可维护性和扩展性。实际开发中应根据具体需求调整界面样式、动画效果和错误处理策略,以提供最佳的用户体验。