基于Flutter跨平台和FastApi轻量级的ChatGPT Web多模态项目, 使用JWT、Mysql、Redis、Sqlalchemy实现用户验签及聊天数据存储;使用Celery、Flower执行并监控后台任务.
- 支持flutter stable(v3.16.8)及使用的pub最新版本 | dart stable(v3.2.5)
- 支持Riverpod(v2.4.9)版本状态管理 | Go_Router处理全局路由
- 支持websocket通信 | record录音及player | markdown 展示文本/语音/图片等消息列表
- 支持JWT验签 | FastApi中间件处理CORS、Exception、Request请求拦截
- 支持Sqlalchemy、mysql、redis存储及缓存用户信息与对话消息API | 采用Celery执行后台任务
- 支持Pinecone向量数据库 | RetrievalAgent检索向量文档
- 支持docker-compose部署
- 支持flutter web多语言展示,yaml文件中的pub库均选择支持多平台应用
- 支持flutter macOS桌面UI | FastApi静态文件访问暂不支持
- 支持Android/iOS/Windows/Linux,移动端适配需要修改UI/UX
.env
.env.prod
- 设置OpenAI api key : https://platform.openai.com/api-keys
- 设置Pinecone向量数据库 api key : https://www.pinecone.io/
- 根据项目需求修改mysql连接地址及其参数配置,DATABASE_URL默认本地地址
- 根据项目需求修改redis连接地址,REDIS_URL默认本地地址
- 根据项目需求修改Celery缓存地址,CELERY_BROKER,CELERY_BACKEND
基于docker部署,如果未安装请先下载 : https://www.docker.com/
启动docker,进入项目ChatGPT-Flutter-Web
docker-compose up -d --build
如果需要查看哪些容器启动成功,执行
docker-compose ps
执行成功后如下图所示
可在docker内查看各容器服务的运行日志
docker-compose logs backend
docker-compose logs frontend
docker-compose logs celery
...
frontend/backend目录下分别有各自的Dockerfile,根目录的docker-compose.yml会执行该配置,backend目录下 提供init.sql用于初始化数据库
- frontend : 127.0.0.1:3000
- backend : 127.0.0.1:8000/docs
- flower : 127.0.0.1:5555
如果部署失败,请根据日志重新修改docker-compose或是项目调整代码,再次执行
docker-compose down
docker-compse up -d --build
ChatGPT-Flutter-Web
├── backend
├── frontend
├── README.md
├── README_zh.md
├── LICENSE
├── docker-compsoe.yml
└── screenshot
- data 目录存放默认的向量化文档,
- app/attach 用于存储上传下载的文件
- db 用于处理Sqlalchemy数据库及表定义操作
├── app
│ ├── api
│ │ ├── **/*.py
│ ├── attach
│ ├── core
│ ├── db
│ ├── main.py
├── data
├── Dockerfile
├── init.sql
├── requirements.txt
└── .gitignore
- pages 目录存放Widget pages
- service 公用服务
- widget 自定义目录存放Widget
- theme 设置Material color
├── lib
│ ├── pages
│ │ ├── **/*.dart
│ ├── service
│ ├── theme
│ ├── widget
│ ├── routers.dart
│ ├── app.dart
│ ├── main.dart
├── assets
├── Dockerfile
├── nginx.conf
├── pubspec.yaml
├── web
├── android
├── ios
├── macos
├── windows
├── linux
└── .gitignore
langchain v0.1.3,较之前的版本官方有包名级别聚合变更,请注意!
pinecone-client v3.0.2,较之前的版本官方有包名级别聚合变更,请注意!
openai v1.9.0,较之前的版本官方有包名级别聚合变更,请注意!
目前已测试并调整为pub仓库最新版本,如有问题请及时提issue
官方文档地址 : https://github.com/dart-lang/markdown
在frontend/lib/widget模块下custom_markdown.dart
///
/// Custom chat message display [markdown] style
/// - [CustomMarkdown]
/// - [CustomAudioTagSyntax]
/// - [CustomAudioBuilder]
/// - [CustomSyntaxHighlighter]
///
class CustomMarkdown extends StatelessWidget {
...
@override
Widget build(BuildContext context) {
return MarkdownBody(
data: displayMsg,
selectable: true,
fitContent: true,
syntaxHighlighter: CustomSyntaxHighlighter(),
styleSheet: MarkdownStyleSheet(
codeblockDecoration: BoxDecoration(
color: Colors.white70, borderRadius: BorderRadius.circular(8))),
onTapLink: (String text, String? href, String title) {
if (href == null || href.isEmpty) return;
launchUrl(Uri.parse(href));
},
imageBuilder: (Uri uri, String? title, String? alt) {
var header = {'Authorization': SpProvider().getString('token')};
return Container(
alignment: Alignment.topLeft,
width: 128,
height: 128,
child: ClipRRect(
borderRadius: const BorderRadius.all(Radius.circular(8)),
child: Image.network(uri.toString(), headers: header)));
},
extensionSet: md.ExtensionSet(md.ExtensionSet.gitHubWeb.blockSyntaxes, [
...md.ExtensionSet.gitHubWeb.inlineSyntaxes,
CustomAudioTagSyntax()
]),
builders: {'audio': CustomAudioBuilder(player)},
);
}
}
项目中实现了简便的处理backend返回带有audio标签tag的文本,用于展示audio并点击播放;同时简便定义code style
CustomAudioTagSyntax
CustomAudioBuilder
CustomSyntaxHighlighter - 代码高亮
输入框跟随文本长度自动换行,复制粘贴时光标滚动到文本末尾multi_modal_input.dart
///
/// Calculate input height
/// [TextPainter.maxWidth] Dynamically calculates the width based on your text characters
///
(double, double) inputHeight(String value) {
final textPainter = TextPainter(
text: TextSpan(text: value, style: const TextStyle(height: 22)),
textDirection: TextDirection.ltr,
maxLines: null)
// ..layout(maxWidth: widget.maxWidth - 16 * 2 - 128);
..layout(maxWidth: 400);
final lineList = textPainter.computeLineMetrics();
final lines = lineList.length;
var lineHeight = lineList.fold(0.0,
(previousValue, element) => previousValue.toDouble() + element.height);
final totalHeight = minInputHeight * (lines == 0 ? 1 : lines);
if (totalHeight > 152) return (152, lineHeight * lines);
return (totalHeight, lineHeight * lines);
}
请根据实际需要修改响应阈值,并调用Riverpod刷新UI状态