鸿蒙应用开发实战之基于 Flutter 的 AI 聊天室实现与 Gemini API 流式集成【跨平台技术在开源鸿蒙中的使用】
鸿蒙应用开发实战之基于 Flutter 的 AI 聊天室实现与 Gemini API 流式集成【跨平台技术在开源鸿蒙中的使用】
随着人工智能技术的发展,AI 聊天室已经成为许多应用的重要功能模块。本文将结合 Flutter 与 Gemini API,详细介绍如何构建一个支持实时流式响应的 AI 聊天室,同时兼顾 UI 展示和复杂 JSON 数据解析的实现技巧。本文的示例将以 Web 展示为主,但同样适用于 OpenHarmony 等生态系统。
项目背景
在之前的项目中,我们已经实现了一个简易的文件管理器,并支持 Markdown 文件的查看。基于这个基础,这次我们希望增加 AI 聊天室功能。由于 Gemini 的免费 API 需要外网访问,这次的 UI 展示采用 Web 网页形式。
在保证跨平台兼容性的同时,Flutter 提供了良好的 UI 渲染能力,能够实现高效、实时的数据展示。核心挑战在于:
- 实现 HTTP 流式通信,实时接收 AI 的回复。
- 处理 Gemini API 返回的 复杂 JSON 数据结构。
- 构建 高性能、沉浸式的聊天 UI,支持 Markdown 渲染和打字机效果。
与 Gemini API 集成
1. 使用 Dio 实现 HTTP 流
我们通过 Dio 来实现对 Gemini API 的访问,并支持流式响应。关键配置如下:
final response = await _dio.post<ResponseBody>(
'/$model:streamGenerateContent',
queryParameters: {'key': _apiKey},
data: {
'contents': contents,
},
options: Options(responseType: ResponseType.stream),
);
responseType: ResponseType.stream允许我们 不等待完整响应体,而是实时接收数据块 (chunk)。- 这样做的好处是 极大降低延迟,在用户输入后可以立即看到 AI 的部分回复。
2. SSE 流式数据解析
Gemini API 返回的是 SSE(Server-Sent Events)流,一个完整的 JSON 事件可能被拆分在多个 chunk,或者一个 chunk 中包含多个事件。如果解析不当,JSON 可能解析失败。
我们使用一个 缓冲区字符串 来管理这些数据:
String buffer = '';
await for (final chunk in stream) {
final text = utf8.decode(chunk);
buffer += text; // 累加到缓冲区
final lines = buffer.split('\n'); // 按行分割
buffer = lines.last; // 将不完整的行保留到缓冲区
for (int i = 0; i < lines.length - 1; i++) {
final line = lines[i].trim();
if (line.startsWith('data: ')) {
final jsonStr = line.substring(6);
try {
final map = jsonDecode(jsonStr) as Map<String, dynamic>;
final candidates = map['candidates'] as List<dynamic>?;
final t = candidates?[0]['content']?['parts']?[0]['text'] as String?;
if (t != null && t.isNotEmpty) {
yield t; // 实时输出给 UI
}
} catch (e) {
if (kDebugMode) {
print('JSON 解析错误: $e');
}
}
} else if (line == 'data: [DONE]') {
return; // 流式结束
}
}
}
这里的重点是:
- 使用 缓冲区 累积数据,保证完整的 JSON 能够被正确解析。
- 使用 安全级联操作符 (
?.) 提取嵌套 JSON 中的文本。 - 通过
yield将流式数据发送给 UI,实现实时显示。
3. Gemini API JSON 响应结构解析
Gemini 返回的文本片段路径复杂:
{
"candidates": [
{
"content": {
"parts": [
{
"text": "AI 回复文本"
}
],
"role": "model"
}
}
]
}
提取方法如下:
final t = candidates?[0]?['content']?['parts']?[0]?['text'] as String?;
通过这种方式,可以 安全地访问深层嵌套的 JSON 字段,避免空指针或异常崩溃。
UI 实现
1. 高效列表展示
聊天界面使用 ListView.builder 构建,可以 动态加载消息,保证性能:
ListView.builder(
controller: _scrollController,
padding: const EdgeInsets.all(16),
itemCount: _messages.length,
itemBuilder: (context, index) {
return _buildMessageBubble(_messages[index], theme);
},
);
2. 单条消息展示
通过 _buildMessageBubble 函数区分 用户消息 和 AI 回复:
- 用户消息:右对齐,浅色背景。
- AI 回复:左对齐,深色背景,支持 Markdown 渲染。
Padding(
padding: const EdgeInsets.symmetric(vertical: 8),
child: Row(
mainAxisAlignment: isUser ? MainAxisAlignment.end : MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (!isUser) ...[
CircleAvatar(
backgroundColor: theme.colorScheme.primaryContainer,
child: Icon(Icons.smart_toy, color: theme.colorScheme.onPrimaryContainer, size: 20),
),
const SizedBox(width: 8),
],
Flexible(
child: Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: isUser ? theme.colorScheme.primary.withOpacity(0.1) : theme.colorScheme.surfaceContainerHighest,
borderRadius: BorderRadius.only(
topLeft: const Radius.circular(16),
topRight: const Radius.circular(16),
bottomLeft: Radius.circular(isUser ? 16 : 4),
bottomRight: Radius.circular(isUser ? 4 : 16),
),
border: Border.all(
color: isUser ? theme.colorScheme.primary.withOpacity(0.3) : theme.colorScheme.outlineVariant,
width: 0.5,
),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (message.content.isEmpty && message.isStreaming)
_buildTypingIndicator(theme)
else
GptMarkdown(message.content),
if (message.isStreaming && message.content.isNotEmpty)
Padding(
padding: const EdgeInsets.only(top: 8),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
SizedBox(
width: 12,
height: 12,
child: CircularProgressIndicator(strokeWidth: 2, color: theme.colorScheme.primary),
),
const SizedBox(width: 6),
Text('生成中...', style: theme.textTheme.bodySmall?.copyWith(color: theme.colorScheme.outline)),
],
),
),
],
),
),
),
if (isUser) ...[
const SizedBox(width: 8),
CircleAvatar(
backgroundColor: theme.colorScheme.primary,
child: Icon(Icons.person, color: theme.colorScheme.onPrimary, size: 20),
),
],
],
),
);
3. 打字机效果
为了增强沉浸感,我们为 AI 回复添加 打字机动画:
_typingTimer = Timer.periodic(const Duration(milliseconds: 40), (t) {
if (!mounted) {
t.cancel();
return;
}
setState(() {
final lastIndex = _messages.length - 1;
final lastMessage = _messages[lastIndex];
final nextIdx = (idx + step) > full.length ? full.length : (idx + step);
_messages[lastIndex] = lastMessage.copyWith(
content: lastMessage.content + full.substring(idx, nextIdx),
);
idx = nextIdx;
if (idx >= full.length) {
_messages[lastIndex] = _messages[lastIndex].copyWith(isStreaming: false);
_isLoading = false;
t.cancel();
}
});
_scrollToBottom();
});
- 利用
Timer.periodic按时间间隔逐步展示文本。 - 每次只追加一小段字符 (
step = 24)。 - 实现 AI 回复逐字生成的动态效果。
最终效果
界面支持:
- 左右分辨用户与 AI 消息
- Markdown 渲染,支持代码块、列表、粗体
- 打字机动画,增加沉浸感
- 滚动到底部自动显示最新消息


总结
通过本文的示例,我们实现了一个 高性能、实时流式的 AI 聊天室:
- 使用 Dio 流式请求,实现底层网络到 UI 的实时数据传输。
- 处理 复杂 JSON 响应结构,安全提取文本。
- 构建 沉浸式聊天 UI,支持 Markdown 渲染和打字机动画。
- UI 可直接迁移至 OpenHarmony 或 Web 平台,只需替换 API 即可。
这个实现为 AI 聊天、知识问答以及智能助手类应用提供了 稳定、高效的技术方案,可作为 Flutter 与 AI 流式应用开发的参考模板。
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
更多推荐

所有评论(0)