📖

図から Flutter のプログラムを書き出してみる

2023/11/19に公開

tldraw, draw-a-ui, makereal について

tldraw というオープンソースの React用ホワイトボードライブラリがありまして、これと gpt-4-vision api を用いて、 図や写真からプログラムを生成するというツールをツイートで見かけました。

https://twitter.com/tldraw/status/1725514334292394470

リポジトリはこれ( draw-a-ui )で、

https://github.com/tldraw/draw-a-ui

それをホスティングしているサイト( makereal )が下記です。

https://makereal.tldraw.com/

使ってみたところ、生成してくれるコードは HTML/JavaScript で、わりかしいい感じに生成してくれます。

今回の主旨

今回はこれを Flutter のコードを吐き出してみてもらえるように修正して遊んでみたので共有です。

こんな感じで図をつくると

下記を生成してくれて、(実際にボタンを押したり入力したりできる)

さらに修正事項を指示すると

直してくれました

作ったものは下記のリポジトリにあります。( 大元の tdlraw/draw-a-ui を fork して、少し修正しました)。 リポジトリの README には 動いてる様子を収めた動画もあるのでチラ見してみて下さい。

https://github.com/kgmyshin/draw-a-ui

動かすには clone して、 yarn して yarn dev してもらえればシュッと動きます。( node のバージョンが古くなければ)

ちなみに最後の生成コードはこんな感じ。

生成コード
import 'package:flutter/material.dart';

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Profile Page',
      theme: ThemeData(
        brightness: Brightness.dark,
        visualDensity: VisualDensity.adaptivePlatformDensity,
      ),
      home: ProfilePage(),
    );
  }
}

class ProfilePage extends StatelessWidget {
  
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: Colors.black,
      body: Padding(
        padding: const EdgeInsets.all(16.0),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: <Widget>[
            SizedBox(height: 40),
            ProfileHeader(),
            SizedBox(height: 24),
            BioSection(),
            SizedBox(height: 24),
            PostSection(),
          ],
        ),
      ),
    );
  }
}

class ProfileHeader extends StatelessWidget {
  
  Widget build(BuildContext context) {
    return Row(
      children: <Widget>[
        CircleAvatar(
          radius: 40,
          backgroundColor: Colors.grey[800],
        ),
        SizedBox(width: 16),
        Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: <Widget>[
            Text(
              'mob',
              style: TextStyle(
                fontSize: 24,
                fontWeight: FontWeight.bold,
              ),
            ),
            Text(
              'bio',
              style: TextStyle(
                fontSize: 18,
              ),
            ),
          ],
        ),
      ],
    );
  }
}

class BioSection extends StatelessWidget {
  
  Widget build(BuildContext context) {
    return Text(
      'hogehoge',
      style: TextStyle(
        fontSize: 16,
      ),
    );
  }
}

class PostSection extends StatelessWidget {
  
  Widget build(BuildContext context) {
    return Expanded(
      child: Container(
        padding: EdgeInsets.all(16),
        decoration: BoxDecoration(
          color: Colors.grey[850],
          borderRadius: BorderRadius.circular(8),
        ),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.stretch,
          children: <Widget>[
            Expanded(
              child: TextField(
                maxLines: null,
                style: TextStyle(fontSize: 16),
                decoration: InputDecoration(
                  hintText: 'What\'s on your mind?',
                  border: InputBorder.none,
                ),
              ),
            ),
            SizedBox(height: 8),
            ElevatedButton(
              onPressed: () {},
              child: Text('go'),
              style: ElevatedButton.styleFrom(
                primary: Colors.grey[800],
                onPrimary: Colors.white,
                shape: RoundedRectangleBorder(
                  borderRadius: BorderRadius.circular(8),
                ),
              ),
            ),
          ],
        ),
      ),
    );
  }
}

Flutterの埋め込み について

Flutter の埋め込みには Dart Pad を使っています。

https://github.com/dart-lang/dart-pad

Dart Pad は公式ドキュメントとかにもある、 実際に触れるアレです ( Interactive example )

今回はソースコードは生成されたタイミングでは非表示で良いので、 flutter_showcase モードを選択しています。

小話をすると Wiki にはそんなモードはないのですが、 コードを見る限りは存在してたので、使わせてもらいました。

https://github.com/dart-lang/dart-pad/blob/d8194c1a243b4d3d6dffc8e2d698d67d2575e7e2/pkgs/dart_pad/lib/embed.dart#L50

モード選択はURLの最後の部分を embed-flutter.html などと指定するのですが、embed-flutter_showcase.html とすればよさそうでした。

所感

makereal.tldraw.com では いい感じの HTML を生成してくれるのですが、 Flutter だとまだ不安定さを感じたというのと、これは人によるかもだけど不安定ゆえにこれだったら自分で書いた方が早いかも?って思うこともありそうというのが正直なところ。

もしかしたらここら辺のプロンプトをいい感じに調整できればもう少し生成されるコードも良くなるかも?

https://github.com/kgmyshin/draw-a-ui/blob/main/app/lib/getDartFromOpenAI.ts#L1-L14

Discussion