LoginSignup
41
31

【個人開発】想像した世界をAIで自由に描けるサービスを作りました【Next.js × Rails API × Three.js】

Last updated at Posted at 2024-04-04

はじめに

こんにちは!

この度、未経験からwebエンジニアへの転職をするため( Gallery.ai ) を開発しました。

GOGP.png

ガク(@Necono_Engineer)と申します。
プログラミングスクールRUNTEQに入学し、学習をしています。

目次

タイトル 備考  
1 サービス概要 このサービスについて
2 開発背景  開発に至った理由について
3 サービスのUI/UX設計  3DUIについて
4 メイン機能の使い方  AI画像生成 & 画像投稿
5 使用技術一覧 
6 インフラ構成 
7 使用技術の選定理由  フロントエンドの採用理由
8 工夫した点  世界観など
9 今後の開発について 
10 終わりに 

1. サービス概要

GOGP.png
Gallery.aiは、誰もが簡単にオリジナルのAIイラストを作成し、それらを3Dバーチャルギャラリーで展示・公開できるプラットフォームです。

▼ サービスURL

▼ Githubリポジトリ

2. 開発背景

当サービスは、私が参加したスクールの自主的なチーム開発がきっかけで始まりました。

チーム内での私の役割はフロント実装・UIデザインでしたが、適切な画像をどのように選ぶかが大きな課題でした。

この問題の解決策として、画像生成AIの力を借りました。
AIが生成した画像を使用することで、サービスの全体的なクオリティを向上させることができました。
その結果、「ユニークな世界観」「これまでに見たことがないサービス」といった多くの肯定的なフィードバックをいただけました。

この体験から画像生成AIに興味を持ち始め

もっと手軽に、ドラマチックな体験として画像生成AIを提供できれば、より多くの人にその魅力を伝えられるのでは?🤔

という思いが生まれ、「Gallery.ai」の構想に至りました。

3. サービスのUI/UX設計

今回、直感的かつユーザーに楽しんでもらえるインターフェイスを実現するために、React Three Fiberライブラリを採用し、Three.jsという3D技術を用いてUI/UXデザインを構築することに焦点を当てました。

個展移動.gif

4. メイン機能の使い方

AI画像生成 画像投稿
AI画像生成機能 画風を選択してAI画像生成ができます。 画像投稿機能 9枚の中から指定した場所に画像を投稿できます。
ログイン/ログアウト機能 リンクXシェア機能
ログイン/ログアウト機能 NextAuth.jsを使用したGoogleログインが可能です。 Xシェア機能 自分の個展へのリンクをXでシェアできます。

5. 使用技術一覧

項目 技術 バージョン
フロントエンド TypeScript / React / Next.js 5.3.3 / 18.2.0 / 14.0.4
バックエンド Ruby / Ruby on Rails 3.2.2 / 7.0.8
インフラ vercel / render
データベース PostgreSQL
認証 NextAuth.js 4.24.5
環境構築 Docker
ストレージ Cloud Storage for Firebase / localForage 10.8.0 / 1.10.0
Web API DALL-E3 API / Web Speech API
UI構築 Three.js 0.160.0
CSSフレームワーク Tailwind CSS / Material-UI 3.3.7 / 5.15.6

6. インフラ構成

image.png

7. 使用技術の選定理由

開発環境

使用技術 : Docker

  • スクールのカリキュラムでDockerでの環境構築に慣れていたこと、環境ごとの差異を最小限に抑えられることからDocker / docker-composeをベースの技術として選びました。

フロントエンド

使用技術: React / TypeScript / Next.js

  • 構想の段階からThree.jsを導入することを考えていたので、豊富なUIライブラリも含めて使用できるReactの採用は必須でした。

▼ React学習のためにまとめた記事

  • また、TypeScriptの記事を書いたことがきっかけで型定義の手法を知り、実際に手を動かして学ぶ必要があると感じました。

▼ TypeScript学習のためにまとめた記事

  • さらに、開発を始めた頃にApp routerが登場し、従来のPages routerとの違いを直接確かめてみたいと思いました。(今回はApp routerを採用しています)

  • チーム開発でNext.jsを使用した経験があり、開発に着手しやすかったため
  • Next.jsの採用によりルーティング設定が容易になる点が魅力的だったため

など、複数の理由からNext.jsを選定しました。

バックエンド

使用技術: Ruby / Ruby on Rails

  • プログラミングスクールで学んできたRuby on Railsを採用することで実装フェーズに迅速に移行できると考えました。

インフラ
使用技術 : vercel / render

  • Vercelに関してはデプロイ時、Next.jsとの相性がいいこと、renderに関しては学習教材やドキュメントの豊富さから採用しました。

認証

使用技術 : NextAuth.js

  • 新規ユーザーが簡単に登録手続きを済ませ、サービスにアクセスできるようGoogleログインを容易に実装できるNextAuth.jsを採用しました。

▼ 参考にした記事

画像用ストレージ

使用技術: Cloud Storage for Firebase

  • Cloud Storage for Firebaseはドキュメントを参照し、必要な設定をプロジェクトに組み込むことで即座に実装が可能だったことから採用しました。

8. 工夫した点

1. サービスの世界観

Image from Gyazo
背景に動く惑星星々を取り入れることで、「宇宙」をテーマにしていることをユーザーに伝わりやすくしました。
テーマカラーをアクアブルーにすることでさらに「海中」のイメージも加え、サービスの広大さと深さを演出しています。

2. TOPページの一覧表示

Cloud Storage for Firebaseを活用して、ランダムに選んだ10枚の画像を取得し、TOPページでCardコンポーネントを使用してユーザーの投稿を一覧表示しています。

app/components/Card.tsx
const fetchImageUrlsFromStorage = async () => {
  const imagesRef = firebaseRef(storage, '/');
  try {
    const imageRefs = await listAll(imagesRef);
    const urlPromises = imageRefs.items.map((itemRef) => getDownloadURL(itemRef));
   let urls = await Promise.all(urlPromises);
    // ランダムに10枚選択するロジックを追加
    urls = urls.sort(() => 0.5 - Math.random()).slice(0, 10);
    console.log('effectUrl', urls);
    return urls;
  } catch (error) {
    console.error("Error fetching image URLs from Firebase Storage:", error);
    return [];
  }
};

TOPページ中央に配置した3D要素は、ユーザーがマウススクロールをすることで回転します。
シンプルな楽しさをコンセプトにユーザーが触れるだけで楽しめるようなデザインにこだわりました。

3. 認証機能

今回、NextAuth.jsでの認証機能を実装しました。

app/api/auth/[...nextauth]/route.ts
import NextAuth from 'next-auth';
import GoogleProvider from 'next-auth/providers/google';
import { Session } from "next-auth";
import { JWT } from "next-auth/jwt";
import axios from 'axios';

const apiUrl = process.env.NEXT_PUBLIC_API_URL;
const secret = process.env.NEXTAUTH_SECRET;
interface Account {
  access_token?: string;
  provider?: string;
}

interface User {
  id?: string;
  name?: string | null;  
  email?: string | null;
 
}
interface MySession extends Session {
  accessToken?: string;
  user_id?: string;
}

interface MyToken extends JWT {
  accessToken?: string;
  user_id?: string;
}
// NextAuthの設定
const nextAuthOptions = {
  providers: [
    GoogleProvider({
      clientId: process.env.GOOGLE_CLIENT_ID || '',
      clientSecret: process.env.GOOGLE_CLIENT_SECRET || '',
      profile(profile) {
        return {
          id: profile.sub, 
          name: profile.name,
          email: profile.email,
          image: profile.picture,
        };
      },
    }),
  ],
  session: {
    strategy: "jwt"as const,
    maxAge: 60 * 60 * 24, // 1日の秒数
    updateAge: 60 * 60, // 1時間ごとにセッションを更新
  },
  secret: process.env.NEXTAUTH_SECRET || '',
  callbacks: {
    async jwt({ token, account, user }: { token: MyToken; account: Account | null; user: User }) {
      if (account && user) {
        token.id = user.id;
        if (account.access_token) {
          token.accessToken = account.access_token;
        }
        if (user.id) {
          token.user_id = user.id;
        }
      }
      return token;
    },
    async session({ session, token }: { session: MySession, token: MyToken }) {
      if (token.accessToken) {
        session.accessToken = token.accessToken;
      }
      if (token.user_id) {
        session.user_id = token.user_id;
      }
      return session;
    },
    async signIn({ user, account }: { user: User, account: Account | null }) {
      if (!account) return false;
			const provider = account?.provider;
			const uid = user?.id;
			const name = user?.name;
			const email = user?.email; 
			
			try {
				const response = await axios.post(
					`${apiUrl}/auth/${provider}/callback`,
					{
						provider,
						uid,
						name,
						email
					}
				);
				if (response.status === 200) {
					return true;
				} else {
					return false;
				}
			} catch (error) {
				return false;
			}
		},
  },
  
};

const handler = NextAuth(nextAuthOptions);
export { handler as GET, handler as POST };

NextAuth.jsの導入により、ログインプロセスが大幅に簡素化されました。
これにより、新規ユーザーがサービスにアクセスしやすくなり、アプリを試すハードルが低下したことで、より多くのユーザーがアプリに触れるようになり、サービスの普及に貢献したと考えています。

4. 説明用ネコ型ロボット「ニャビくん」

Image from Gyazo

マウスの方向を向いてくれます。

Image from Gyazo
クリックするとサービスの説明をしてくれます。

5. 生成画像の画風指定 & 音声入力機能

image.png
AI画像生成機能では、18種類の異なる画風から選択して、イラストを作成できます。

さらに、Web Speech APIを活用した音声入力機能も備えており、マイクボタンを押して話しかけるだけで、AI画像生成が可能です。

9. 今後の改善点

1. リファクタリング

機能の実装に重きを置いて実装を進めすぎたので、今後は他の人がより読みやすく、Reactのコンポーネント思想に準拠した設計をしていこうと考えています。

2. フルスタック化

これまではUIやフロントエンドの構築に多くの時間を割いてきましたが、今後はバックエンドの機能充実にも力を入れる予定です。

10. おわりに

このポートフォリオを作成する過程で、多くの人に助けられました。スクールの同期や講師の方々、リリース前にサービスを触ってくれた方々、忙しい中でも話を聞いてくれたり、アドバイスをくれた皆様に、感謝しています。

本当にありがとうございました!!!🙇‍♂️

まだまだ未熟なところばかりですので、これからも楽しみながら学習を続けていければと思います!

長くなりましたが、お読みいただいた皆様、ありがとうございました!!

Xもやっているので、よければフォローお願いします🐈

41
31
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
41
31