我用 Flutter Gemini 寫了一個水貼 APP
影片
https://youtu.be/sEpJWfNwbmk
https://www.bilibili.com/video/BV1gH4y177sy/
前言
原文 https://ducafecat.com/blog/flutter-gemini-ai-integration
本文透過 Flutter 外掛 google_generative_ai 快速的整合了 google ai gemini 來實現一個水貼的工具。
程式碼
https://github.com/ducafecat/flutter_develop_tips/tree/main/flutter_application_gemini
gemini 介紹
門戶站
https://gemini.google.com/
開發站
https://ai.google.dev/
Google Cloud 收費
https://console.cloud.google.com
參考
https://medium.com/flutter/harness-the-gemini-api-in-your-dar...
https://ai.google.dev/tutorials/dart_quickstart?hl=zh-cn
https://ai.google.dev/models/gemini?hl=zh-cn
技術限制
限制國家 IP
限制模擬器執行,需要真機執行
準備工作
獲取 gemini api key
https://aistudio.google.com/app/apikey?hl=zh-cn
模型說明
https://ai.google.dev/models/gemini?hl=zh-cn
測試有效
請求地址 url
https://generativelanguage.googleapis.com/v1beta/models/gemini-pro:generateContent?key=YOUR_API_KEY
引數
{
"contents": [
{
"parts": [
{
"text": "介紹下如何快速學習 Flutter."
}
]
}
]
}
postman 測試
Flutter 開發步驟
新增 pub 包
pubspec.yaml
dependencies:
flutter:
sdk: flutter
# The following adds the Cupertino Icons font to your application.
# Use with the CupertinoIcons class for iOS style icons.
cupertino_icons: ^1.0.2
google_generative_ai: ^0.2.1
dio: ^5.4.1
flutter_markdown: ^0.6.20
外掛包站:
https://flutter.ducafecat.com
https://pub.dev
準備圖片
pubspec.yaml
# The following section is specific to Flutter packages.
flutter:
# The following line ensures that the Material Icons font is
# included with your application, so that you can use the icons in
# the material Icons class.
uses-material-design: true
# To add assets to your application, add an assets section, like this:
assets:
- assets/images/
準備了 1.jpg 2.jpg , 一會讓 gemini 識別下圖片內容。
配置 API Key
.vscode/launch.json
{
// 使用 IntelliSense 瞭解相關屬性。
// 懸停以檢視現有屬性的描述。
// 欲瞭解更多資訊,請訪問: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "flutter_application_gemini",
"request": "launch",
"type": "dart",
"flutterMode": "debug",
"program": "lib/main.dart",
"args": ["--dart-define=API_KEY=you key"]
},
{
"name": "flutter_application_gemini (profile mode)",
"request": "launch",
"type": "dart",
"flutterMode": "profile"
},
{
"name": "flutter_application_gemini (release mode)",
"request": "launch",
"type": "dart",
"flutterMode": "release"
}
]
}
lib/main.dart
class MyApp extends StatelessWidget {
const MyApp({super.key});
static String apiKey = const String.fromEnvironment('API_KEY');
......
首頁
lib/index.dart
標題
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Google Gemini AI 水貼'),
),
body: _buildView(),
);
}
主檢視
Widget _buildView() {
return Container(
padding: const EdgeInsets.all(10),
child: GridView.extent(
maxCrossAxisExtent: 150,
mainAxisSpacing: 20,
crossAxisSpacing: 20,
children: <Widget>[
// 1 內容生成
_buildItem(
"內容生成",
onTap: () => Navigator.push(
context,
MaterialPageRoute(builder: (context) => const ContentPage()),
),
),
// 2 流失內容生成
_buildItem(
"流失內容",
onTap: () => Navigator.push(
context,
MaterialPageRoute(builder: (context) => const StreamPage()),
),
),
// 3 圖片識別
_buildItem(
"圖片識別",
onTap: () => Navigator.push(
context,
MaterialPageRoute(builder: (context) => const VersionPage()),
),
),
],
),
);
}
單元格
Widget _buildItem(String title, {Function()? onTap}) {
return GestureDetector(
onTap: onTap,
child: Container(
decoration: BoxDecoration(
border: Border.all(color: Colors.grey),
),
child: Center(
child: Text(
title,
style: const TextStyle(fontSize: 18),
)),
),
);
}
內容生成
lib/content.dart
標題、傳送、複製
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('內容生成'),
actions: [
// 複製
IconButton(
onPressed: () {
Clipboard.setData(ClipboardData(text: _content));
},
icon: const Icon(Icons.copy),
),
// 提交
IconButton(
onPressed: () async {
var res = await _doContentGeneration(_textController.text);
setState(() {
_content = res ?? "";
});
},
icon: const Icon(Icons.send),
)
],
),
body: _buildView(),
);
}
輸入框
final TextEditingController _textController = TextEditingController(
text: "你現在是論壇水貼王子,請介紹下油管up主貓哥(ducafecat), 他講 flutter 技術,而且黑暗巫術也很好。");
@override
void dispose() {
_textController.dispose();
super.dispose();
}
ai 生成
/// ai 內容
String _content = "";
/// 生成文字內容
Future<String?> _doContentGeneration(String value) async {
// 生成模型
final model = GenerativeModel(
// 模型名稱
model: 'gemini-pro',
// API 金鑰
apiKey: MyApp.apiKey,
// 根據可能的有害性調整您看到回覆的可能性。基於內容有害性的機率進行遮蔽。
safetySettings: [
SafetySetting(HarmCategory.harassment, HarmBlockThreshold.medium), // 騷擾
SafetySetting(
HarmCategory.hateSpeech, HarmBlockThreshold.medium), // 仇恨言論
SafetySetting(
HarmCategory.sexuallyExplicit, HarmBlockThreshold.medium), // x暗示
SafetySetting(
HarmCategory.dangerousContent, HarmBlockThreshold.medium), // 危險內容
],
);
// 提問詞列表
final content = [
Content.text(value),
];
// 請求返回
final response = await model.generateContent(content);
return response.text;
}
主檢視
Widget _buildView() {
return Column(
children: [
// 輸入框
TextField(
controller: _textController,
maxLines: 3,
decoration: const InputDecoration(
labelText: '輸入你的提示詞',
),
),
// 內容
Expanded(child: Markdown(data: _content)),
],
);
}
流失內容
lib/stream.dart
流式文字內容
/// 生成文字內容
Future<void> _doContentStream(String value) async {
// 生成模型
final model = GenerativeModel(
// 模型名稱
model: 'gemini-pro',
// API 金鑰
apiKey: MyApp.apiKey,
// 根據可能的有害性調整您看到回覆的可能性。基於內容有害性的機率進行遮蔽。
safetySettings: [
SafetySetting(HarmCategory.harassment, HarmBlockThreshold.medium), // 騷擾
SafetySetting(
HarmCategory.hateSpeech, HarmBlockThreshold.medium), // 仇恨言論
SafetySetting(
HarmCategory.sexuallyExplicit, HarmBlockThreshold.medium), // x暗示
SafetySetting(
HarmCategory.dangerousContent, HarmBlockThreshold.medium), // 危險內容
],
);
// 提問詞列表
final content = [
Content.text(value),
];
// 清空
setState(() {
_content = "";
});
// 流失接收
model.generateContentStream(content).listen((event) {
setState(() {
_content += event.text ?? "";
});
});
}
圖片識別
lib/vision.dart
build
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('圖片識別'),
actions: [
// 複製
IconButton(
onPressed: () {
Clipboard.setData(ClipboardData(text: _content));
},
icon: const Icon(Icons.copy),
),
// 提交
IconButton(
onPressed: () async {
var res = await _doVisionGeneration(_textController.text);
setState(() {
_content = res ?? "";
});
},
icon: const Icon(Icons.send),
)
],
),
body: _buildView(),
);
}
主檢視
Widget _buildView() {
return Column(
children: [
// 輸入框
TextField(
controller: _textController,
// maxLines: 2,
decoration: const InputDecoration(
labelText: '輸入你的提示詞',
),
),
// 圖片列表
SizedBox(
height: 160,
child: _buildImagesList(),
),
// 內容
Expanded(child: Markdown(data: _content)),
],
);
}
輸入框
final TextEditingController _textController =
TextEditingController(text: "這兩張圖片是關於什麼內容?");
@override
void dispose() {
_textController.dispose();
super.dispose();
}
圖片列表
/// 圖片列表
Widget _buildImagesList() {
return Container(
padding: const EdgeInsets.all(10),
child: GridView.extent(
maxCrossAxisExtent: 150,
mainAxisSpacing: 20,
crossAxisSpacing: 20,
children: <Widget>[
Image.asset("assets/images/1.jpg"),
Image.asset("assets/images/2.jpg"),
],
),
);
}
圖片識別
/// ai 內容
String _content = "";
/// 讀取圖片
Future<Uint8List> loadImage(String path) async {
final ByteData data = await rootBundle.load(path);
return data.buffer.asUint8List();
}
/// 圖片識別
Future<String?> _doVisionGeneration(String value) async {
// 生成模型
final model = GenerativeModel(
// 模型名稱
model: 'gemini-pro-vision',
// API 金鑰
apiKey: MyApp.apiKey,
// 根據可能的有害性調整您看到回覆的可能性。基於內容有害性的機率進行遮蔽。
safetySettings: [
SafetySetting(HarmCategory.harassment, HarmBlockThreshold.medium), // 騷擾
SafetySetting(
HarmCategory.hateSpeech, HarmBlockThreshold.medium), // 仇恨言論
SafetySetting(
HarmCategory.sexuallyExplicit, HarmBlockThreshold.medium), // x暗示
SafetySetting(
HarmCategory.dangerousContent, HarmBlockThreshold.medium), // 危險內容
],
);
// 提問詞列表
final (firstImage, secondImage) = await (
loadImage('assets/images/1.jpg'),
loadImage('assets/images/2.jpg'),
// File('assets/images/1.jpg').readAsBytes(),
// File('assets/images/1.jpg').readAsBytes()
).wait;
final prompt = TextPart(value);
final imageParts = [
DataPart('image/jpeg', firstImage),
DataPart('image/jpeg', secondImage),
];
final response = await model.generateContent([
Content.multi([prompt, ...imageParts])
]);
// 請求返回
return response.text;
}
程式碼
https://github.com/ducafecat/flutter_develop_tips/tree/main/flutter_application_gemini
小結
感謝閱讀本文
如果有什麼建議,請在評論中讓我知道。我很樂意改進。
© 貓哥
ducafecat.com
end