Flutter 文本对话¶
本文档介绍在 Flutter 中实现聊天功能,支持流式输出、多轮上下文和系统提示词。
功能预览¶

左侧导航选择 💬 文本对话 → 输入消息 → AI 流式回复,呈现打字机效果
目录¶
基础概念¶
文本对话 API 的核心请求体包含三个部分:
{
'model': 'gpt-4o-mini', // 模型名称
'messages': [...], // 对话历史,user/assistant交替
'stream': true/false, // 是否启用流式输出
}
常用模型:
gpt-4o-mini/gpt-4o- OpenAI 系列deepseek-chat/deepseek-v3- DeepSeek 系列claude-sonnet-4-6- Anthropic 系列
简单单轮对话¶
Future<String> simpleChat(String message) async {
final response = await http.post(
Uri.parse('${dotenv.env['APINEXUS_BASE_URL']!}/chat/completions'),
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer ${dotenv.env['APINEXUS_API_KEY']!}',
},
body: json.encode({
'model': 'gpt-4o-mini',
'messages': [
{'role': 'user', 'content': message}
],
}),
);
final data = json.decode(response.body);
return data['choices'][0]['message']['content'];
}
流式输出 SSE¶
流式输出可以获得打字机效果,体验更好:
import 'package:flutter/foundation.dart';
Stream<String> streamChat(String message) async* {
final request = http.Request(
'POST',
Uri.parse('${dotenv.env['APINEXUS_BASE_URL']!}/chat/completions'),
);
request.headers['Content-Type'] = 'application/json';
request.headers['Authorization'] =
'Bearer ${dotenv.env['APINEXUS_API_KEY']!}';
request.body = json.encode({
'model': 'gpt-4o-mini',
'stream': true,
'messages': [
{'role': 'user', 'content': message}
],
});
final response = await request.send();
String fullText = '';
await for (final chunk in response.stream.transform(utf8.decoder)) {
for (final line in chunk.split('\n')) {
if (line.startsWith('data: ')) {
final data = line.substring(6);
if (data == '[DONE]') continue;
try {
final jsonData = json.decode(data);
final delta =
jsonData['choices'][0]['delta']['content'] ?? '';
if (delta != null && delta.isNotEmpty) {
fullText += delta;
yield fullText;
}
} catch (_) {}
}
}
}
}
在 UI 中使用:
StreamBuilder<String>(
stream: streamChat('用一句话介绍 Flutter'),
builder: (context, snapshot) {
return Text(snapshot.data ?? '正在输入...');
},
)
多轮对话上下文¶
将历史消息累积到 messages 数组中:
class ChatSession {
final List<Map<String, String>> messages = [];
void addUser(String content) {
messages.add({'role': 'user', 'content': content});
}
void addAssistant(String content) {
messages.add({'role': 'assistant', 'content': content});
}
Future<String> send() async {
final response = await http.post(
Uri.parse('${dotenv.env['APINEXUS_BASE_URL']!}/chat/completions'),
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer ${dotenv.env['APINEXUS_API_KEY']!}',
},
body: json.encode({
'model': 'gpt-4o-mini',
'messages': messages,
}),
);
final data = json.decode(response.body);
final reply = data['choices'][0]['message']['content'];
addAssistant(reply);
return reply;
}
}
系统提示词¶
通过设置 system role 的消息,可以控制 AI 的行为:
messages = [
{
'role': 'system',
'content': '你是一位专业的 Flutter 开发者顾问。'
'回答应简洁、包含代码示例,并标注关键注意事项。'
},
{'role': 'user', 'content': '如何实现滚动列表懒加载?'}
]
常用参数¶
| 参数 | 类型 | 说明 | 默认值 |
|---|---|---|---|
model | string | 模型名称 | 必填 |
temperature | number | 随机性 (0-2) | 1.0 |
max_tokens | number | 最大输出 Token 数 | 可变 |
stream | boolean | 是否流式输出 | false |
top_p | number | 核采样阈值 | 1.0 |
完整示例¶
一个完整的聊天应用的核心组件:
class ChatPage extends StatefulWidget {
const ChatPage({super.key});
@override
State<ChatPage> createState() => _ChatPageState();
}
class _ChatPageState extends State<ChatPage> {
final List<Map<String, String>> _messages = [];
final _controller = TextEditingController();
bool _isStreaming = false;
String _currentReply = '';
Future<void> _send() async {
if (_controller.text.isEmpty || _isStreaming) return;
setState(() {
_messages.add({'role': 'user', 'content': _controller.text});
_isStreaming = true;
_currentReply = '';
});
_controller.clear();
final history = List<Map<String, String>>.from(_messages);
await for (final text in streamChatToAI(history)) {
setState(() => _currentReply = text);
}
setState(() {
_messages.add({'role': 'assistant', 'content': _currentReply});
_isStreaming = false;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('AI 对话')),
body: Column(
children: [
Expanded(
child: ListView.builder(
itemCount: _messages.length + (_isStreaming ? 1 : 0),
itemBuilder: (_, i) {
if (i < _messages.length) {
final msg = _messages[i];
return ListTile(
title: Text(msg['content']!),
subtitle: Text(msg['role']!),
);
}
return ListTile(title: Text(_currentReply));
},
),
),
TextField(controller: _controller),
ElevatedButton(onPressed: _send, child: const Text('发送')),
],
),
);
}
}