Gemini APIとReactでimgタグを自動生成するAIアプリを作りました
コンニチハ!、プライベートでよくReactを触っているプログラミングが好きな27歳男性です。
最近、自分の時間で少しずつ個人開発していたWebアプリが完成したので、そのアプリの紹介をします!
アプリの紹介
選択した画像をGemini APIで解析し、altテキストを生成してimgタグを自動生成します。
通常のAIチャットアプリでも、画像を渡してaltテキストを生成、imgタグを自動生成することはできますが、このアプリでは画像のファイル名、パスの指定、width、heightの値も確実にimgタグとして生成するのでよりimgタグ生成に特化しています。
アプリの使い方
ドロップゾーンに画像をドラッグ&ドロップして生成ボタンを押すだけでimgタグを生成できます。
ドロップゾーン上部の設定欄からsrc属性の画像ファイルまでのパスやloading属性などの属性値を指定することもできます。
生成ボタンを押すと画像プレビューと共に以下のようなimgタグが生成され、コピーボタンを押せばimgタグをコピーできます。
AIがaltテキスト候補を掲示してくれているので、3つの中から好きなテキストを選んでalt属性に指定します。
需要があるかは分かりませんが、Next.jsにはお世話になっているのでnext/imageのタグを表示するオプションもあります。
アプリを作った理由
この記事を見ている方は、おそらくコーディングの経験があると思います。
その際、imgタグを書く時には altテキストを筆頭にwidth、heightの値やsrc属性など、imgタグを出現率の割に書かないといけないものが多すぎるんですよね。
僕自身、わずかではありますがWeb制作の実務経験もあって、このimgタグの記述には多くの時間を割かれることがありました。
そこで、このimgタグの記述を楽にしてくれるアプリを作りました。
技術スタック
- フレームワーク : Next.js
- スタイリング : Tailwind CSS
- AI : Gemini 1.5 flash
- ホスティングサービス : Cloudflare Pages
- テスト : Vitest
Next.js/React
サーバーサイド言語未習得なのと、CRUD処理などのないシンプルなアプリなので、手軽にフルスタック開発できるNext.jsを選択しました。
また、構想段階から後述のCloudflare Pagesにデプロイすることを計画していたので、その点でもかなりNext.jsは相性が良かったです。
とはいってもNext.jsとしての機能を使っているのはAPIやルーティングのみで、大部分はクライアントコンポーネントですw
主な使用ライブラリは以下の通りです。
ライブラリ名 | 用途 |
---|---|
@google/generative-ai | 画像を解析してaltテキスト生成 |
zod | アップロードされた画像の型チェック |
shadcn/ui | UIコンポーネント |
react-dropzone | 画像をドラッグ&ドロップでアップロード |
react-syntax-highlighter | コードにシンタックスハイライトを当てる |
@google/generative-ai
このサービスのコアとなるライブラリなので紹介します。
プログラミング初心者でも扱えそうなくらい簡単にTypeScriptでGemini APIを叩けます。
以下は実際にアプリ内でAPI RoutesからGemini APIを叩いているコードです。(関連するコードのみ抜粋)
import { GoogleGenerativeAI } from '@google/generative-ai';
// APIキーを引数にGoogleGenerativeAIのインスタンスを生成
const genAI = new GoogleGenerativeAI(process.env.GEMINI_API_KEY as string);
// モデルを生成
const model = genAI.getGenerativeModel({
model: "gemini-1.5-flash",
generationConfig: { responseMimeType: "application/json" }, // JSON形式で返す設定
});
// zodでパースした画像ファイルをモデルに渡すためのデータを生成
const inlineData: inlineData[] = validFiles;
// 画像ファイルをモデルに渡すためのデータを生成
const fileToGenerativePart: fileToGenerativePartProps = (file) => {
return {
inlineData: {
data: file.base64, // 画像ファイルをbase64に変換したものをデータとして渡す
mimeType: file.mimeType, // 画像ファイルのMIMEタイプを渡す
},
};
};
// モデルに渡すプロンプト
const prompt = `
あなたは画像のalt属性を生成してJSONを返すAPIです。
画像の説明を日本語で生成してください。
画像ごとに3つずつaltテキスト候補を生成してください。
JSONの形式は以下の通りです。"必ず以下のJSON形式で返してください。"
{
"altTexts": {
${files.map(
(file, index) =>
`"img${index + 1}": {
"altText1": "altテキスト1",
"altText2": "altテキスト2",
"altText3": "altテキスト3"
}`
)}
}
}
`;
try {
// モデルに渡すデータを生成
const generatedContent = await model.generateContent([prompt, ...inlineData.map(fileToGenerativePart)]);
// 生成されたaltテキストをJSONとして返す
const json = JSON.parse(generatedContent.response.text());
return NextResponse.json(json);
} catch (error) {
/* -----エラー処理----- */
}
いかがでしょうか?
実際にはGemini APIに渡すデータ容量を減らすために、画像を圧縮してからbase64に変換していたり、アップロードされた画像をzodで型チェックしていたりしますが、核となる部分はこれだけです。
実際にリクエストを送信すると以下のようなJSONが返ってきます。なんて便利なんだ。。。。!!
{
"altTexts": {
"img1": {
"altText1": "ソーセージ、目玉焼き、千切りキャベツのシンプルな朝食プレート",
"altText2": "ミニソーセージが並んだ、目玉焼きと千切りキャベツの彩り豊かな朝食",
"altText3": "白いお皿に盛られた、ソーセージ、目玉焼き、キャベツのヘルシーな朝食"
}
}
}
Tailwind CSS
Tailwind CSSを選定した理由は単純にスピーディーなスタイリングができるからです。
今回のアプリでは実用性重視でデザインはシンプルにしているので、Tailwind CSSで十分にスタイリングできました。
Gemini 1.5 flash
Gemini 1.5 flashを選んだ一番の理由は、無料枠があることです。
1500リクエスト/日、15リクエスト/分という制限がありますが、素人の個人開発アプリでは十分な制限です。
また、Gemini APIにはJSONモードという設定があり
const model = genAI.getGenerativeModel({
model: "gemini-1.5-flash",
generationConfig: { responseMimeType: "application/json" }, // JSON形式で返す設定
});
このように引数で設定するだけで、生成されるものがJSON形式であることを保証できるので、その点でもかなり使いやすいです。
アイデア次第で色んなアプリが開発できそうなので、これからも使っていきたいです。
Cloudflare Pages
Cloudflare Pagesを選んだ理由は、Next.jsが簡単にデプロイできて、無料枠でも商用利用可能だからです。
今回のアプリではマネタイズの部分は考えずに開発することにしたのでVercelでも十分ですが、Cloudflare Pagesにデプロイする練習も兼ねていたので選びました。
Cloudflareが提供するnext_on_pages
を使えば、Next.jsのプロジェクトをCloudflare Pagesに簡単にデプロイすることができます。
別記事でデプロイでハマった点などを書いているので、よろしければそちらもご覧ください。
開発をふりかえって
今回あえて状態管理ライブラリを使わずuseState
で全ての状態を管理していたのですが、
オブジェクトのディープコピーの挙動が難しくていい勉強になりました。
まだまだ不要なコンポーネントの再レンダリングなど発生していると思うので、のんびりリファクタリングしていきたいです🍵
また、今回は実際のアプリ開発になるべく近づけたかったのでイシューやユースケース図などの作成からテストコードの実装まで色々チャレンジしてみました。
事前にドキュメントを作成したことで頭の中が整理されたかなりスムーズに開発できたので、この辺の作業はしっかりやっていきたいです!
おわりに
内容の規模がどうであれ、一人で開発したアプリを世の中に公開する達成感はクセになりますね🔥
今回は認証機能やCRUD処理などのないシンプルなアプリだったので、次はもう少しレベルアップしたアプリを作ってみたいです!
その際はまた記事にしてみたいと思います。
それではよい個人開発ライフを!
Discussion