我用 Flutter Gemini 写了一个水贴 APP

我用 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-dart-and-flutter-apps-00573e560381

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

html 复制代码
https://generativelanguage.googleapis.com/v1beta/models/gemini-pro:generateContent?key=YOUR_API_KEY

参数

json 复制代码
{
    "contents": [
        {
            "parts": [
                {
                    "text": "介绍下如何快速学习 Flutter."
                }
            ]
        }
    ]
}

postman 测试

Flutter 开发步骤

添加 pub 包

pubspec.yaml

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

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

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

dart 复制代码
class MyApp extends StatelessWidget {
  const MyApp({super.key});

  static String apiKey = const String.fromEnvironment('API_KEY');
  ......

首页

lib/index.dart

标题

dart 复制代码
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Google Gemini AI 水贴'),
      ),
      body: _buildView(),
    );
  }

主视图

dart 复制代码
  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()),
            ),
          ),
        ],
      ),
    );
  }

单元格

dart 复制代码
  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

标题、发送、复制

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(),
    );
  }

输入框

dart 复制代码
  final TextEditingController _textController = TextEditingController(
      text: "你现在是论坛水贴王子,请介绍下油管up主猫哥(ducafecat), 他讲 flutter 技术,而且黑暗巫术也很好。");
dart 复制代码
  @override
  void dispose() {
    _textController.dispose();
    super.dispose();
  }

ai 生成

dart 复制代码
  /// ai 内容
  String _content = "";
dart 复制代码
  /// 生成文字内容
  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;
  }

主视图

dart 复制代码
  Widget _buildView() {
    return Column(
      children: [
        // 输入框
        TextField(
          controller: _textController,
          maxLines: 3,
          decoration: const InputDecoration(
            labelText: '输入你的提示词',
          ),
        ),

        // 内容
        Expanded(child: Markdown(data: _content)),
      ],
    );
  }

流失内容

lib/stream.dart

流式文字内容

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

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 _doVisionGeneration(_textController.text);
              setState(() {
                _content = res ?? "";
              });
            },
            icon: const Icon(Icons.send),
          )
        ],
      ),
      body: _buildView(),
    );
  }

主视图

dart 复制代码
  Widget _buildView() {
    return Column(
      children: [
        // 输入框
        TextField(
          controller: _textController,
          // maxLines: 2,
          decoration: const InputDecoration(
            labelText: '输入你的提示词',
          ),
        ),

        // 图片列表
        SizedBox(
          height: 160,
          child: _buildImagesList(),
        ),

        // 内容
        Expanded(child: Markdown(data: _content)),
      ],
    );
  }

输入框

dart 复制代码
  final TextEditingController _textController =
      TextEditingController(text: "这两张图片是关于什么内容?");
dart 复制代码
  @override
  void dispose() {
    _textController.dispose();
    super.dispose();
  }

图片列表

dart 复制代码
  /// 图片列表
  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"),
        ],
      ),
    );
  }

图片识别

dart 复制代码
  /// ai 内容
  String _content = "";
dart 复制代码
  /// 读取图片
  Future<Uint8List> loadImage(String path) async {
    final ByteData data = await rootBundle.load(path);
    return data.buffer.asUint8List();
  }
dart 复制代码
  /// 图片识别
  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

相关推荐
恋猫de小郭7 小时前
2026 Flutter VS React Native ,同时在 AI 时代 VS Native 开发,你没见过的版本
android·前端·flutter
明君8799711 小时前
Flutter 如何给图片添加多行文字水印
前端·flutter
后端AI实验室15 小时前
我用Cursor开发了3个月,整理出这套提效4倍的工作流
java·ai
牧马人win18 小时前
Microsoft Agent Framework 详解与实践
ai
四眼肥鱼19 小时前
flutter 利用flutter_libserialport 实现SQ800 串口通信
前端·flutter
妙妙屋(zy)1 天前
Windows系统安装OpenClaw并使用Qwen千问接入飞书教程 🤖
ai
Johny_Zhao1 天前
OpenClaw安装部署教程
linux·人工智能·ai·云计算·系统运维·openclaw
火柴就是我1 天前
让我们实现一个更好看的内部阴影按钮
android·flutter
王晓枫1 天前
flutter接入三方库运行报错:Error running pod install
前端·flutter