Contentful の記事投稿を ChatGPT を使って効率化してみた

# やりたいこと
Contentful で記事を書く際に文章を考えるのが面倒なので、重要なポイントだけ箇条書きにして、あとは自動で文章を生成してほしいと思っています。


# 実現方法
Contentful の UI Extension を使って実現します。
UI Extensions では Contentful の画面をカスタムできるもので、サイドバーの機能やフィールドの機能を JavaScript ベースのコーディングで拡張できます。

https://www.contentful.com/developers/docs/extensibility/ui-extensions/


今回は、以下のような仕様で UI Extensions を作ってみます。
- ChatGPT への命令内容を入力するフィールド (Field名: input)
- ChatGPT が生成した文章の内容を出力するフィールド (Field名: output)
- input に入力した後にボタンを押下し、API 経由で ChatGPT に文章を作成してもらう
- UI Extensions のタイプはフィールド機能を拡張する「Field extensions」を利用します
    - 参考：[UI extensions - Locations and types](https://www.contentful.com/developers/docs/extensibility/ui-extensions/locations/#field-extensions)

下図が完成後のイメージです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/162398/8d7d8708-cb59-5fb6-ee88-4a7d2b8b9847.png)

この記事ではこれから実装手順が長く続きます。実際に作ってみて有用だと感じたユースケースについては「[最後に](#最後に)」の章に記載しているので、実装に興味がない方はそちらをご覧ください。

# 実装

では早速取り掛かっていきます。

## 1. UI Extensions の作成

まず UI Extensions を作成していきます。

### 1.1 プロジェクトの作成

Contentful の公式サイトにある手順でプロジェクトを作成します。

https://www.contentful.com/developers/docs/extensibility/app-framework/tutorial/

Createコマンドを叩き、今回は「Template」を元に作成しました。

```shell
% npx create-contentful-app contentful-with-chatgpt
Creating a Contentful app in ~/Developer/contentful-with-chatgpt.
? Do you want to start with a blank template or use one of our examples? (Use arrow keys)
❯ Template 
  Example 
```

次に開発するフレームワークを選択します。
TypeScript と JavaScript 含め、一番下の Vue 以外はすべて React ベースです。Contentful は基本的に React ベースで作られているようなので、特段の理由がない限りは Vue 以外を選んだ方が良いと思います。今回は Contentful 公式の情報源が多そうな TypeScript のテンプレートを選択しました。

```shell-session
% npx create-contentful-app contentful-with-chatgpt
Creating a Contentful app in ~/Developer/contentful-with-chatgpt.
? Do you want to start with a blank template or use one of our examples? Template
? Pick a template 
❯ TypeScript 
  JavaScript 
  Next.js 
  React + Vite 
  Vue 
```

ここまで終えるとプロジェクトが作成され、 `npm start` を実行するようにサジェストされます。もちろんまだ何も実装していないので実行して何か起こるわけではありませんが、とりあえず実行してみます。


```shell-session
% cd contentful-with-chatgpt
% npm start 
```
問題なくローカルサーバ起動すればOKです。

### 1.2 OpenAI パッケージインストール
続いて、ChatGPT の API を呼び出すため、OpenAI のパッケージをインストールします。
手順は以下の公式ドキュメントにある通りです。

https://platform.openai.com/docs/libraries

```shell-session
% npm install openai        

added 2 packages, and audited 1568 packages in 2s

246 packages are looking for funding
  run `npm fund` for details

6 high severity vulnerabilities

To address all issues (including breaking changes), run:
  npm audit fix --force

Run `npm audit` for details.
```

特にエラーが発生しなければ OK です。

先に、この後 OpenAI の API 呼び出しで使うクライアントを作成する処理を実装しておきます。


```ts:src/lib/openai.ts
import { Configuration, OpenAIApi } from "openai";

export const createClient = (param: {
  organization: string;
  apiKey: string;
}) => {
  const configuration = new Configuration({
    organization: param.organization,
    apiKey: param.apiKey,
  });
  return new OpenAIApi(configuration);
};
```

### 1.3 UI Extension 本体の実装

冒頭にも書きましたが今回はフィールド機能を拡張する「Field extensions」というタイプの UI Extensions を利用するため、 `src/locations/Field.tsx` にコードを書いていきます。
特筆すべき点を列挙しておきます。
- ボタン押下で `generateComment` メソッドが呼び出され、API 経由で ChatGPT にリクエストを送り、レスポンスの中から回答を output フィールドに出力させます。
- 以下の３点については [parameters](https://www.contentful.com/developers/docs/extensibility/ui-extensions/parameters/) という仕組みで外部から値を挿入できるようにします。
    - outputフィールドのID (parameters.instance.outputFiledId)
    - OpenAIのOrganization (parameters.instance.openaiOrganization)
    - OpenAIのAPI Key (parameters.instance.openaiApiKey)
- parameters には「installation parameters」と「instance parameters」の２種類がありますが、今回は実装が簡単な「instance parameters」を使います。
- スタイルの実装は Contentful 公式のコンポーネントライブラリ [Forma 36](https://f36.contentful.com/) を利用します。
- Textarea コンポーネントは長文用のテキストボックスで、ユーザが高さを変えることができます。それに応じてフィールドを囲むエリアの大きさも自動調整して欲しいので [AutoResizer](https://www.contentful.com/developers/docs/extensibility/app-framework/react-apps-toolkit/) を有効化しておきます。

今回のコードは以下のようになります。

```tsx:src/locations/Field.tsx
import React, { useState } from "react";
import { Box, Text, Button, Textarea } from "@contentful/f36-components";
import { FieldExtensionSDK } from "@contentful/app-sdk";
import {
  useAutoResizer,
  useSDK,
  useFieldValue,
} from "@contentful/react-apps-toolkit";
import { createClient } from "../lib/openai";

const Field = () => {
  useAutoResizer();
  const sdk = useSDK<FieldExtensionSDK>();
  const OUTPUT_FILED_ID = sdk.parameters.instance.outputFiledId;
  const openai = createClient({
    organization: sdk.parameters.instance.openaiOrganization,
    apiKey: sdk.parameters.instance.openaiApiKey,
  });

  const [inputText, setInputText] = useFieldValue<string>();
  const [, setOutputText] = useFieldValue<string>(OUTPUT_FILED_ID);
  const [isLoading, setIsLoading] = useState(false);

  const generateComment = async () => {
    if (!inputText) return;
    setIsLoading(true);

    try {
      const response = await openai.createChatCompletion({
        model: "gpt-3.5-turbo",
        messages: [{ role: "user", content: inputText }],
      });
      setOutputText(response.data.choices[0].message?.content ?? "");
    } catch (error) {
      console.log(error);
    }

    setIsLoading(false);
  };

  return (
    <>
      <Textarea
        defaultValue={inputText}
        onChange={(e) => setInputText(e.target.value)}
      />
      <Box marginBottom="spacingXs">
        <Text fontColor={"gray600"} fontWeight={"fontWeightMedium"}>
          {`ChatGPTへのインプットを入力してボタンを押下してください`}
        </Text>
      </Box>
      <Box>
        <Button
          variant="secondary"
          isLoading={isLoading}
          isDisabled={!inputText || !!isLoading}
          onClick={generateComment}
        >
          Generate
        </Button>
      </Box>
    </>
  );
};

export default Field;
```

## 2. ローカル環境 UI Extensions のインストールと設定

ここまでで UI Extensions の実装は完了したので、実際に Contentful にインストールして、起動したローカルサーバと接続してテストできる状態にします。

### 2.1 Create app

Contentful にログインし、 `Organization Settings > Apps > Create app` と進みます。

まず、Name と Frontend を設定します。まだローカルサーバと接続してテストする段階なので Frontend には `localhost` を入力します。ポート番号は `.env` で設定した内容と合わせてください。デフォルトは3000のはずです。


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/162398/ac0064ec-06fa-8868-9ec2-042257caeb85.png)

次に、Locations を設定します。今回は Field Extensions を作成しているので、「Entry Field」を選びます。また、箇条書きで文を入力する想定なので「Long text」を選択します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/162398/3e45e6f9-a79d-2a22-22cb-c079dd5ce41b.png)

続いて「instance parameters」を設定します。デフォルト値には適切な値を設定してください。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/162398/51ea9ee5-9653-87cd-2861-3b268f28847a.png)

### 2.2 Install app
ここまで終わえると、Organization Settings ではなく Space の画面に遷移し Apps を開き、先ほど作成した UI Extensions をこの Space へインストールします。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/162398/67dfbd2f-b780-1e7b-57c5-0c2943cb913a.png)

### 2.3 Content Model の設定
続いて、Content Model から該当のモデルを開き、「Add field」ボタンを押下して、output フィールドと input フィールドを追加します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/162398/087de517-56e0-d94f-89c1-72f8770e8295.png)

両方とも Text タイプを選択します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/162398/e4fddfce-2aa3-5b61-6c22-dcfd8e561ad1.png)

両方とも Long text タイプを選択します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/162398/52b6e8f1-9411-c1ca-f4bc-947b6615a522.png)

output フィールドの Appearance は Multiple line で問題ありませんが、 input フィールドの Appearance は、作成した UI Extensions を指定します。Instalation parametes の値がデフォルト値のままだとまずい場合はここで変更します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/162398/ddbe8188-257e-7c60-d4f6-145e26c6b400.png)

以下のようになりました。Save を押下して実際に試してみます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/162398/02fc6f32-e6a0-6eb6-7837-fea61be76e4d.png)

## 3. テスト
Content を開き、「Add Entry」を押下します。
開いた画面で input フィールドに ChatGPT への依頼事項を記載し、「Generate」を押下すると...、数秒経ってから output フィールドに ChatGPT からの返答が表示されます！！

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/162398/f1dea4fb-8da8-8655-dae5-2331adce2224.png)


https://dev.classmethod.jp/articles/contentful-ui-extension-editor/

## 4. 本番インストール

全て問題なさそうであれば、公式サイト手順で deploy & hosting します。

https://www.contentful.com/developers/docs/extensibility/app-framework/deploy-app/

https://www.contentful.com/developers/docs/extensibility/app-framework/hosting-an-app/

# 最後に
今回は Contentful の UI Extension に ChatGPT を組み込み、記事を作成させてみました。

この記事ではお試しで作成したので、単純に ChatGPT への input と output を項目として用意するだけになりましたが、実際にコンテンツ運用されている方は、他に記事のタイトルやタグなど設定が必須な項目がたくさんあると思うので、それらを元に ChatGPT への入力文字列を組み立てるようなプログラムを構築すれば、よりコンテンツ作成者の最小限の労力で同様の結果を得られるようになると思います。

ただ、ブログ記事全体を出力するのは「入力を書くぐらいなら自分で書いた方が早い！」という意見もあると思うので、以下のようなユースケースが適していると思います。

- ブログではないコンテンツの中でちょこっと文章が必要な部分（ECサイトの商品紹介や、商品紹介サイトのコメントなど？）
- ネイティブ言語でない言語でコンテンツ作成が必要な場合

特に、後者に関しては、英語の文章が必要な際に「○○の文章を英語で書いて」と ChatGPT にお願いすると、翻訳ツールを使うよりもナチュラルな英語を作ってくれます。こちらは日本語で要点を書いているだけなでナチュラルな英語が出力されるので、ものすごい省力化につながります。

今回作成した UI Extensions の実際のコードは、GitHubで公開しています。
興味がある方はぜひ利用してみてください。

https://github.com/kanejun-x/contentful-with-chatgpt