我用 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

相关推荐
孤鸿玉20 分钟前
Fluter InteractiveViewer 与ScrollView滑动冲突问题解决
flutter
叽哥7 小时前
Flutter Riverpod上手指南
android·flutter·ios
BG1 天前
Flutter 简仿Excel表格组件介绍
flutter
zhangmeng1 天前
FlutterBoost在iOS26真机运行崩溃问题
flutter·app·swift
恋猫de小郭1 天前
对于普通程序员来说 AI 是什么?AI 究竟用的是什么?
前端·flutter·ai编程
卡尔特斯1 天前
Flutter A GlobalKey was used multipletimes inside one widget'schild list.The ...
flutter
w_y_fan1 天前
Flutter 滚动组件总结
前端·flutter
醉过才知酒浓1 天前
Flutter Getx 的页面传参
flutter
火柴就是我2 天前
flutter 之真手势冲突处理
android·flutter
Speed1232 天前
`mockito` 的核心“打桩”规则
flutter·dart