🎃

ねえ、一緒にかっこいいAI POWEREDの画像生成ウェブサイトをReactで作らない?

2023/10/06に公開

今暇でしょう? 作ろう! いいじゃん!

これです:

あ、テンション上がりすぎて挨拶を忘れました、すみません、金曜日だし。。。

¡Hola! こんにちは! テラーノベルのオスカルです。Webの開発をしてます。いつも言うんだけど、日本語はまだまだ勉強してますので、応援してください!

Create ai-love-images react app

最初から始めましょう、まずはcreate-react-appを使って新しいフレッシュなReactプロジェクトを作成します。

npx create-react-app ai-love-images

いい名前でしょう?w

おそらく既知ですが、念のために説明します。前のコマンドは非常にシンプルなReactアプリケーションの構造を作成し、ai-love-imagesフォルダに移動してnpm startを実行すると、ブラウザで標準のReactプロジェクトウェルカム画面が表示され、http://localhost:3000でアクセスできます。

メインのアプリケーションレイアウトは app.js にあり、スタイルは app.css にあります。 TypeScript を実装し、ChakraAnt Design のような UI フレームワークを追加するのが理想的でしたが、これは素早いコンセプト証明プロジェクトですので、標準の React コンポーネントを使用します。まずは、素敵な背景グラデーションスタイルを追加しましょう。

Basic UI

app.jsはこんな感じでわかりやすいだろう?

import './app.css';
const App = () => {
  return (
    <div className="app">
      <h1>¡Hola!</h1>
    </div>
  )
}

export default App;

メニューやその他の要素は必要ありませんので、メインのラッパーは利用可能なスペース全体を使い、内部のコンテンツを水平および垂直に中央に配置します。

app.cssはこんな感じ:

.app {
  text-align: center;
  background: rgb(2,0,36);
  background: linear-gradient(90deg, rgba(2,0,36,1) 0%, rgba(9,9,121,1) 35%, rgba(0,212,255,1) 100%);
  height: 100vh;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
}

h1 {
  color: white;
}

あ、この背景グラデーションCSSは、特定のウェブサイト、CSS Gradient から直接取得しました。

good enough !

src フォルダ内に components という名前のフォルダを作成しましょう。
components フォルダ内にもう1つのフォルダを作成し、それに image-showcase という名前を付けましょう。これは OpenAI API を使用して生成されるメインの大きな画像をレンダリングするコンポーネントになります。

assets フォルダ内に、私たちの愛される「テノちゃん」の写真を追加し、次のような 3 つのファイルを追加しました:

以下の内容:

index.js

// コンポーネントのインポートを簡略化するために
export * from "./image-showcase"

image-showcase.jsx

import tenoChanImage from "./assets/teno-chan-photo.png"
import "./image-showcase.css"

export const ImageShowcase = () => {
  return (
  <div className="imageWrapper">
    <img src={tenoChanImage} alt="Everybody loves TenoChan" />
  </div>
)}

image-showcase.css

.imageWrapper > img {
  max-width: 300px;
  border-radius: 40px;
  border: 3px solid white;
  box-shadow: 0 1px 1px rgba(0,0,0,0.12), 0 2px 2px rgba(0,0,0,0.12);
}

ボックスシャドウのCSSを生成するためにこのページを使用してください。

次に、テキスト入力コンポーネントを作成しましょう。この場合、text-input-box という名前のフォルダを追加し、以下のファイルを追加しました:

index.js

// コンポーネントのインポートを簡略化するために
export * from "./text-input-box"

text-input-box.jsx

import tenoChanIcon from "./assets/teno-chan.png"
import "./text-input-box.css";

export const TextInputBox = () => {
  return (
    <form onSubmit={() => alert("TODO")}>
      <div className="input-box">
        <button className="btn-input">
	  <img src={tenoChanIcon} width="50" alt="tenochan" />
	 </button>
        <input type="text" className="input-text" placeholder="テラーノベルの可愛いテノちゃん" />    
      </div>
    </form>
  )}

text-input-box.css

.input-box{
  margin-top: 50px;
  box-sizing: border-box;
  width: fit-content;
  height: fit-content;
  position: relative;
  font-family: 'Lato' !important;
}
.input-text{
  height: 50px;
  width: 50px;
  border-style: none;
  padding: 10px;
  font-size: 18px;
  letter-spacing: 2px;
  outline: none;
  border-radius: 25px;
  transition: all .5s ease-in-out;
  background-color: rgb(22,90,172);
  border: 2px solid white;
  color: transparent;
}
.input-text::placeholder{
  font-size: 18px;
  letter-spacing: 2px;
  font-weight: 100;
  color: transparent;
}
.btn-input{
  width: 50px;
  height: 50px;
  border-style: none;
  font-size: 20px;
  font-weight: bold;
  outline: none;
  cursor: pointer;
  border-radius: 50%;
  position: absolute;
  right: 16px;
  top: 10px;
  color:#ffffff ;
  background-color:transparent;
  pointer-events: painted;
}

.btn-input:focus ~ .input-text{
  width: 300px;
  border-radius: 0px;
  background-color: transparent;
  border: none;
  padding-right: 60px;
  border-bottom:1px solid rgba(255,255,255,.5);
  transition: all 500ms cubic-bezier(0, 0.110, 0.35, 2);
  color: #fff;
}

.btn-input:focus ~ .input-text::placeholder {
  color:rgba(255,255,255,.5);
}

.input-text:focus{
  width: 300px;
  border-radius: 0px;
  background-color: transparent;
  border: none;
  border-bottom:1px solid rgba(255,255,255,.5);
  transition: all 500ms cubic-bezier(0, 0.110, 0.35, 2);
  padding-right: 60px;
  color: #fff;
}

.input-text:focus::placeholder {
  color:rgba(255,255,255,.5);
}

あ、すみません! このCSSはかなり高度です(100%理解していない部分もあります 😅 )。実はこのリンクからインスパイアを受け、それを私たちのテノちゃんを追加するために変更しました。

ところでfreefrontend.comtympanus.netはとても便利です!

app.jsにコンポーネントを入れたら:

import './app.css';
import { TextInputBox } from './components/text-input-box';
import { ImageShowcase } from './components/image-showcase';

const App = () => {
  return (
    <div className="app">
      <ImageShowcase  />
      <TextInputBox />
    </div>
  )
}
export default App;

現時点では、プレーンなCSS + HTMLコンポーネントしかありませんが、今のところユーザープロンプトを取得するためのコードを追加しましょう。このために、各コンポーネントに use-local-hook.js ファイルを追加することをお勧めします。それでは、text-input-box フォルダでそれを行いましょう:

/text-input-box/use-local-hook.js

import { useCallback, useRef } from 'react';

export const useLocalHook = () => {
  const promptRef = useRef();
 
  const handleSubmit = useCallback((e) =>{
    e.preventDefault();
    console.log(promptRef.current.value);
  }, []);

  return {
    handleSubmit,
    promptRef
  }
}

フォームの送信イベントを処理し、ページのリロードを防止します(これはフォームの標準動作です)。また、useRef を使用して入力テキストの値を取得します。これはテキストの値を取得する方法の1つであり、他のアイデアについてはこのリンクをご確認ください。より複雑なフォームに対しては、useForm ライブラリをお勧めします。

TextInputBoxコンポーネントから私たちのフックをインポートし、入力への参照とフォームの送信処理を追加しましょう:

text-input-box.jsx

import tenoChanIcon from "./assets/teno-chan.png"
import "./text-input-box.css";
import { useLocalHook } from "./use-local-hook";

export const TextInputBox = () => {
  const { promptRef, handleSubmit} = useLocalHook();
  return (
    <form onSubmit={handleSubmit}> // <-- ここ
      <div className="input-box">
        <button className="btn-input">
		<img src={tenoChanIcon} width="50" alt="tenochan"/>
	</button>
	//            ↓ ここ
        <input ref={promptRef} type="text" className="input-text" placeholder="テラーノベルの可愛いテノちゃん" />    
      </div>
    </form>
  )}

何かを入力してENTERキーを押すと、フォームが送信され、入力値がコンソールにログとして表示されます。

OPEN-AI APIを入れましょう!

基本的なUIができましたので、ユーザープロンプトを使用してAI APIにリクエストを送信し、レスポンスで受け取る生成された画像を待ちましょう。この際、私たちはChatGPTDALL-Eの作者であるOpenAIのAPIを使用しますが、注意が必要です。APIは無料では提供されていないため、料金が発生しますが、高額ではありません。

まず、https://platform.openai.com/ にアカウントを作成しましょう。アカウントの作成自体は無料です。

アカウントが作成されたら、私たちの小さなアプリケーションでリクエストを送信するために使用されるAPIキーを取得する必要があります。個人のメニューから、View API keys に移動してください。あそこからCreate new secret keyで新しいキーを生成し、どこかに記録してください。

APIは無料ではないため、APIリクエストが400エラーを返さないようにするために、クレジットカード情報を追加し、初期資金を入れる必要があります。これはサイドメニューの「Billing - Overview」セクションで行えます。

テスト用に10ドルを追加し、テスト中に1ドルも使用されていないことを確認しました。試してみるにはかなり安価ですが、本番サービスに使用する前に必ず価格のウェブサイトを確認してください!。

APIキーを直接クライアント側から使用すると、それを公開し、セキュリティのリスクとなります。詳細についてはこちらをご覧ください。そのため、Honoを使用して異なるプロジェクトを作成し、独自のAPIを実装します。このAPIは、OpenAI APIに対するリクエストを代行します。

「え?別のプロジェクト?」 😅

はいはい、心配しないでください。とても簡単でシンプルです。ただ水を飲んで、私についてきてください。💪

こんな感じですね:

Create Hono ai-love-open-ai app

では、ai-love-imagesプロジェクトフォルダの外に出て、非常にシンプルなHonoプロジェクトを作成するために、このコマンドを実行してください。また、事前に定義されたテンプレートを要求します。簡単にするために、bun を選択してください。

npm create hono@latest ai-love-open-ai

ai-love-open-aiフォルダ内に、.envファイルを作成し、OpenAI APIキーをOPENAI_API_KEYキーと一緒に追加してください。このキーは共有せず、どんなリポジトリにもコミットしないようにしてください!

.env

OPENAI_API_KEY = "sk-****"

OpenAIには私たちにとって作業を簡略化するためのパッケージもありますので、どうぞインストールしてください。

bun install openai

honoはAPIハンドラの作成を非常に簡単にします。単に/src/index.tsにいくつかのコードを追加すれば、すぐに動作します。

コード内にコメントを追加して、理解しやすくしました。基本的に、リクエストを受け取り、APIキーが正しく設定され、ユーザーのプロンプトがあるかどうかを確認し、すべてが正しければ、OpenAIパッケージを使用して彼らのAPIにリクエストを送信し、レスポンスを処理します。

/src/index.ts

import { Hono } from "hono";
import OpenAI from "openai";

// .envファイルに設定されたAPIキーを使用してオブジェクトをインスタンス化します
const openAI = new OpenAI({
  apiKey: process.env.OPENAI_API_KEY,
});

const app = new Hono();

app.post("/api/open-ai/image-generator", async (c) => {
  const body = await c.req.json();
  const { prompt } = body;

  if (!openAI.apiKey) {
    return c.json({ error: "OpenAI API key not set" }, { status: 500 });
  }

  if (!prompt) {
    return c.json({ error: "Prompt not provided" }, { status: 400 });
  }

  // https://platform.openai.com/docs/api-reference/images/object
  const openAIResponse = await openAI.images.generate({
    prompt, // ユーザーのプロンプト
    n: 1, // 生成する画像の数 
    size: "512x512" // 画像サイズ
  })

  // OpenAIの応答を記録して、何が来ているか確認する
  console.log(openAIResponse);

  return c.json({ ok: true }); // for now
});

export default app;

このサーバーをポート3001で起動して、確認しましょう:

PORT=3001 bun run dev

可愛い猫をプロンプトとして画像生成のために、http://localhost:3001/api/open-ai/image-generatorPOSTリクエストを送信しましょう。この curl コマンドを使えば、ターミナルから簡単に行うことができます。

curl -X POST -H "Content-Type: application/json" -d '{"prompt": "可愛い猫"}' http://localhost:3001/api/open-ai/image-generator

少し時間がかかるかもしれませんが、curlコマンドには{"ok":true}の応答が返ります。しかし、Honoアプリケーションのログを確認すると、OpenAI APIからの応答が表示されます。

{
  created: 1696552736,
  data: [
    {
      url: 'https://oaidalleapiprodscus.blob.core.windows.net/private/org-GHrdXOF8ECjVKbsAPk6EJyjF/user-HKqdJ2Flf0qv4Qdc7vNx6F5u/img-b8BAvSH2lef46vgGGw6Ejc2T.png?st=2023-10-05T23%3A38%3A56Z&se=2023-10-06T01%3A38%3A56Z&sp=r&sv=2021-08-06&sr=b&rscd=inline&rsct=image/png&skoid=6aaadede-4fb3-4698-a8f6-684d7786b067&sktid=a48cca56-e6da-484e-a814-9c849652bcb3&skt=2023-10-05T22%3A19%3A10Z&ske=2023-10-06T22%3A19%3A10Z&sks=b&skv=2021-08-06&sig=Diew4BsgCDfoTFlAPhDhQPuIJ/MY6zcGNLcbpJj71f4%3D'
    }
  ]
}

これですね:

デフォルトでは、CORSの設定により、異なるオリジンからのリクエストは許可されません。私たちのReactアプリケーションはポート3000で実行されているため、/api/open-ai/image-generatorエンドポイントにアクセスできません。これを正しく設定するためにはさまざまな方法がありますが、この例では簡単に保つことにします。index.tsファイルの先頭に次のコードを追加してください:

/src/index.ts

import { Hono } from "hono";
import OpenAI from "openai";
import { cors } from "hono/cors"; // <-- ここ

[..]

const app = new Hono();
// ↓ これ
app.use(
  "/api/*",
  cors({
    origin: "http://localhost:3000",
    allowMethods: ["POST"],
  })
);

[..]

これにより、プロジェクトはhttp://localhost:3000からのAPIエンドポイントへのPOSTリクエストを許可するように設定されます。OpenAI APIが返すデータの種類をすでに知っているため、画像のURLを返すことができます。エンドポイントの戻り値部分を置き換えましょう:

/src/index.ts


[..]

  // https://platform.openai.com/docs/api-reference/images/object
  const openAIResponse = await openAI.images.generate({
    prompt, // ユーザーのプロンプト
    n: 1, // 生成する画像の数 
    size: "512x512" // 画像サイズ
  })
  
  // ↓ ここ
  if(!openAIResponse.data || !openAIResponse.data[0] || !openAIResponse.data[0].url) {
    return c.json({ error: "OpenAI API response invalid" }, {status:500});
  }
  
  return c.json({ url: openAIResponse.data[0].url})
});

export default app;

それで当分のHonoアプリケーションは完了ですね!それほど悪くなかったですね、そうでしょう? 😊

Back to React ai-love-images app!

使えそう! アプリにAPIキーとユーザープロンプトを含むリクエストを送信するための機能を追加し、簡略化のために、src/hooks フォルダ内にカスタムの一般的なフックを作成します:

/src/hooks/use-image-generate-request-hook.js

const IMAGE_GENERATOR_API_ENDPOINT =  "http://localhost:3001/api/open-ai/image-generator";

const sendRequest = async (prompt) => {
  const response = await fetch(
    IMAGE_GENERATOR_API_ENDPOINT,
    {
      method: "POST",
      headers: {
        "Content-Type":"application/json",
      },
      body: JSON.stringify({
        prompt,
      })
    }
  )
  
  return(response.json());
}

次の操作は、ユーザーが入力した内容をリンクし、sendRequest 関数を介して送信し、レスポンスを確認することです。

この機能をフックコード内に追加し、それを返して TextInputBox コンポーネントから直接使用できるようにします。

/src/hooks/use-image-generate-request-hook.js

const IMAGE_GENERATOR_API_ENDPOINT =  "http://localhost:3001/api/open-ai/image-generator";

const sendRequest = async (prompt) => {
  [..] // 変わらず
}

export const useImageGenerateRequestHook = () => {
  const sendPrompt = useCallback(async (prompt) => {
    if (!prompt) return; // 空のリクエストを送信しないように
    const response = await sendRequest(prompt);
    console.log(response);
  }, []);

  return {
    sendPrompt
  }
}

この関数を TextInputBox コンポーネントのプロパティとして受け取り、コンポーネントのローカルフックに渡します。

text-input-box.jsx

import tenoChanIcon from "./assets/teno-chan.png"
import "./text-input-box.css";
import { useLocalHook } from "./use-local-hook";

export const TextInputBox = ({sendPrompt}) => {

  const { promptRef, handleSubmit} = useLocalHook(sendPrompt);
  
  return (
    <form onSubmit={handleSubmit}>
    [..] // 変わらず
    

/text-input-box/use-local-hook.js

import { useCallback, useRef } from 'react';

export const useLocalHook = (sendPrompt) => {

  const promptRef = useRef();
  
  const handleSubmit = useCallback((e) =>{
    e.preventDefault();
    sendPrompt(promptRef.current.value);
  }, [sendPrompt]);

  return {
    handleSubmit,
    promptRef
  }
}

app.jsを介してすべてを結びつけるプロセスを説明します:

app.js

import { useCallback, useState } from 'react';
import './app.css';
import { TextInputBox } from './components/text-input-box';
import { ImageShowcase } from './components/image-showcase';
import { useImageGenerateRequestHook } from './hooks/use-image-generate-request-hook';

const App = () => {
  const { sendPrompt } = useImageGenerateRequestHook();
  return (
    <div className="app">
      <ImageShowcase/>
      <TextInputBox sendPrompt={sendPrompt} />
    </div>
  )
}

export default App;

テストしましょう! 可愛い猫と入力し、Enter キーを押して少し待つと、次のような応答が表示されます:

{ 
  "url": "https://oaidalleapiprodscus.blob.core.windows.net/private/org-GHrdXOF8ECjVKbsAPk6EJyjF/user-HKqdJ2Flf0qv4Qdc7vNx6F5u/img-v6pVbc5hutdWduWvX7Y8nmPi.png?st=2023-10-04T10%3A00%3A11Z&se=2023-10-04T12%3A00%3A11Z&sp=r&sv=2021-08-06&sr=b&rscd=inline&rsct=image/png&skoid=6aaadede-4fb3-4698-a8f6-684d7786b067&sktid=a48cca56-e6da-484e-a814-9c849652bcb3&skt=2023-10-04T01%3A37%3A31Z&ske=2023-10-05T01%3A37%3A31Z&sks=b&skv=2021-08-06&sig=tpLwtG%2BsPtLVjTOdkdMut6WGXLvHDXy2GWxmWDFK3EU%3D"
}

これですね!

Joining response from API to image component

生成された画像のURLを取得し、それを ImageShowcase コンポーネントに渡して、送信ごとに変更されるようにします。

use-image-generate-request-hook.js

import { useCallback, useState } from "react";

const sendRequest = async (prompt) => {
  [..]
}

export const useImageGenerateRequestHook = () => {
  const [img, setImgUrl] = useState(null);  // <-- ここ

  const sendPrompt = useCallback(async (prompt) => {
    if (!prompt) return;
    const response = await sendRequest(prompt);
    setImgUrl(response.url) // <-- ここ
  }, []);

  return {
    sendPrompt,
    img  // <-- ここ
  }
}

image-showcase.jsx

import tenoChanImage from "./assets/teno-chan-photo.png"
import "./image-showcase.css"

export const ImageShowcase = ({src}) => {
  return (
  <div className="imageWrapper"}>
    <img src={src || tenoChanImage} alt="Everybody loves TenoChan" />
  </div>
)}

app.js

import { useCallback, useState } from 'react';
import './app.css';
import { TextInputBox } from './components/text-input-box';
import { ImageShowcase } from './components/image-showcase';
import { useImageGenerateRequestHook } from './hooks/use-image-generate-request-hook';

const App = () => {
  const { sendPrompt, img } = useImageGenerateRequestHook();
  return (
    <div className="app">
      <ImageShowcase src={img} />
      <TextInputBox sendPrompt={sendPrompt} />
    </div>
  )
}
export default App;

この追加により、APIからの応答が受け取られた後に画像が更新されます!

Add loading animation

出たけど、結構時間かかりますね。。。ちょっとあれだからローディングアニメーションを入れましょう!

use-image-generate-request-hook.js

import { useCallback, useState } from "react";

const sendRequest = async (prompt) => {
  [..]
}

export const useImageGenerateRequestHook = () => {
  const [img, setImgUrl] = useState(null);  
  const [isLoading, setLoading] = useState(false); // <-- ここ

  const sendPrompt = useCallback(async (prompt) => {
    if (!prompt) return;
    setLoading(true); // <-- ここ
    const response = await sendRequest(prompt);
    setLoading(false); // <-- ここ
    setImgUrl(response.url)
  }, []);

  return {
    sendPrompt,
    img,
    isLoading // <-- ここ
  }
}

ImageShowcase コンポーネント内で、ローディングフラグに応じてCSSアニメーションを追加しましょう:

image-showcase.jsx

import tenoChanImage from "./assets/teno-chan-photo.png"
import "./image-showcase.css"

export const ImageShowcase = ({src, isLoading}) => {
  return (
  <div className="imageWrapper">
    <img src={src || tenoChanImage} alt="Everybody loves TenoChan" />
    <div className={isLoading ? "loading full" : "loading"}></div> // <-- ここ
  </div>
)}

image-showcase.css

.loading {
  width: 0;
  margin-left: 30px;
  height: 8px;
  background: red;
}

.loading.full {
  width: 256px;
  transition: 15s;
}

このシンプルなアニメーション技術では、赤い背景を持つ div 要素が、0から256pxまでの幅に15秒かけて展開されます。これはAPIの平均応答時間を表しています。

また、isLoading フラグを app.js からコンポーネントに渡す必要があります

app.js

const App = () => {
  const { sendPrompt, img, isLoading } = useImageGenerateRequestHook();
  return (
    <div className="app">
      <ImageShowcase src={img} isLoading={isLoading} />
      <TextInputBox sendPrompt={sendPrompt} />
    </div>
  )
}
export default App;

良くなりました (英語かスペイン語か日本語。。。どちらでもかまいません!):

Let's add some more animations

アニメーションが好きなので、もっとクールなエフェクトを追加させてください 😄

text-input-box.jsx

// ここもisLoadingを使います            ↓
export const TextInputBox = ({sendPrompt, isLoading }) => {

  const { promptRef, handleSubmit} = useLocalHook(sendPrompt);
  
  return (
    <form onSubmit={handleSubmit}>
      <div className="input-box">
        <button className="btn-input">
	  <img src={tenoChanIcon} width="50" alt="tenochan" className={`${isLoading ? "animate" : ""}`}/> // <-- ここ
	 </button>
        <input ref={promptRef} type="text" className="input-text" placeholder="テラーノベルの可愛いテノちゃん" />    
      </div>
    </form>
  )}

text-input-box.css

@keyframes tada {
  0% {
    transform: scale3d(1, 1, 1);
  }

  10%, 20% {
    transform: scale3d(.9, .9, .9) rotate3d(0, 0, 1, -3deg);
  }

  30%, 50%, 70%, 90% {
    transform: scale3d(1.1, 1.1, 1.1) rotate3d(0, 0, 1, 3deg);
  }

  40%, 60%, 80% {
    transform: scale3d(1.1, 1.1, 1.1) rotate3d(0, 0, 1, -3deg);
  }

  100% {
    transform: scale3d(1, 1, 1);
  }
}

img.animate {
  animation-name: tada;
  animation-duration: 1s;
  animation-iteration-count: infinite;
  animation-delay: 1s;
}

app.js

const App = () => {
  const { sendPrompt, img, isLoading } = useImageGenerateRequestHook();
  return (
    <div className="app">
      <ImageShowcase src={img} isLoading={isLoading} />
      <TextInputBox sendPrompt={sendPrompt} isLoading={isLoading} />
    </div>
  )
}
export default App;

↑これはテノちゃんのアイコンのアニメーションです。

次は新しい画像に変更しそうとき、ImageShowcaseも動かす:

image-showcase.jsx

import tenoChanImage from "./assets/teno-chan-photo.png"
import "./image-showcase.css"

export const ImageShowcase = ({src, isLoading}) => {
  return (
  //                               ここ ↓
  <div className={`imageWrapper ${isLoading === false ? "animate" : ""}`}>
    <img src={src || tenoChanImage} alt="Everybody loves TenoChan" />
    <div className={isLoading ? "loading full" : "loading"}></div>
  </div>
)}

image-showcase.css

@keyframes gelatine {
  from, to { transform: scale(1, 1); }
  25% { transform: scale(0.9, 1.1); }
  50% { transform: scale(1.1, 0.9); }
  75% { transform: scale(0.95, 1.05); }
}

.imageWrapper > img {
  max-width: 300px;
  border-radius: 40px;
  border: 3px solid white;
  box-shadow: 0 1px 1px rgba(0,0,0,0.12), 0 2px 2px rgba(0,0,0,0.12);
}

.imageWrapper.animate > img {
  animation: gelatine 1s;
}

.loading {
  width: 0;
  margin-left: 30px;
  height: 8px;
  background: red;
}

.loading.full {
  width: 256px;
  transition: 15s;
}

これが現時点での最終結果です、どう思いますか?

Ghe?!? なんか、GOOD BYEじゃないよね。。。w 😅

ところで、こんなテストいっぱいしたけど、まだ$10から$9.51があります。。。全然使えそう!

そして、今のところはこれで終わりかなと思います... もっと改善のアイデアがあります。例えば、類義語を追加し、テキスト解析を行い、ユーザープロンプトのさまざまなバージョンを生成し、複数の画像を一度に生成することも考えています。さらに、他のAPIを使用して、1つのプロンプトから異なるスタイルの多くの異なる画像を取得することもできます(以下の2つはかなり面白いです:EmojiGen および AI Emojis)。しかし、この記事はすでに長くなってしまったので、これらのアイデアはまた別の機会に取り組むことにしましょう。

Clone the code from Github!

あ!コードはここにあります、是非cloneして遊んでください!

ai-love-open-ai

ai-love-images

Adios! Happy generating! .

読んでいただき、ありがとうございます!

テラーノベル テックブログ

Discussion