【個人開発】技術記事作成をアシストしてくれる万能記事エディタアプリを開発しました！

# はじめに
投稿予定のQiitaの記事について、AIがアドバイスをしてくれるアプリ（アプリ名：Clear Draft）を開発しました！
初の個人開発ということで0からのアプリ作成はなかなか大変でしたが、苦労しながらもなんとか完成させることができましたので、本記事では、このアプリの詳細な機能のご紹介と、開発過程で得た知見、直面した課題、そしてそこから学んだ教訓などを共有させていただきます！

ちなみに、**<font color="Red">`私がReactに初めて触れたのは約1か月前で、Reactの基本すら知らなかった状態からスタート`</font>** しました。最初は試行錯誤の連続でしたが、少しずつ学びながらアプリを作り上げていった結果、ここまでのものを完成させることができました。本記事では、アプリ開発から学んだアウトプットの重要性についてもご紹介させていただきますので、是非お読みいただければ嬉しいです！

<p align="center">

<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3832049/585816c9-0fc3-b4a9-1f88-83c04d7330c4.png" />

</p>

※本アプリはプログラミングコミュニティ「JISOU」の自由課題として作成したものです。

# Clear Draftを作った理由と実装した機能
Qiita投稿支援アプリ「Clear Draft」は、<font color="Red">`技術記事の作成と投稿プロセスを効率化したい`</font>という思いで開発しました。
このアプリは、生成AIを活用して文章の改善やアドバイスを受ける際の手間を削減し、ワンクリックで支援を得られる機能を提供します。
Markdownエディタ、プレビュー機能、AIアシスタント、Qiitaへの投稿機能を一つのアプリに統合することで、複数のツールを行き来する必要をなくし、執筆環境を一元化しました。
また、保存、編集、削除機能を備えた記事一覧画面により、ユーザーは記事管理を容易に行うことができます。

<p align="center">
<img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3832049/39571f5b-fd93-0022-14d8-1c2982bad7c6.png" />
</p>

# 機能紹介

### 概要
技術記事の執筆を効率化するために、生成AI(Gemini API)による文章改善やアドバイス、Markdownエディタ、Qiitaへの直接投稿機能を一元化したアプリ


### 技術スタック
```
- 環境
vite: 5.4.1
firebase: 13.16.0

- 言語
typescript: 5.5.3

- ライブラリ
react: 18.3.1
react-hook-form: 7.52.2
react-router-dom: 6.26.0
axios: 1.7.3
@google/generative-ai: 0.17.1

- DB
supabase/supabase-js: 2.45.0

- CSS
chakra-ui: 2.8.2

- Test
testing-library/jest-dom: 6.4.8
testing-library/react: 16.0.0
testing-library/user-event: 14.5.2
types/jest: 29.5.12
```

### 機能デモ

- **記事編集画面**
![スクリーンショット 2024-09-03 20.35.33（3）.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3832049/e77eaf42-a3ca-d4a8-2fed-90b5a94425e8.png)
画面の左側にはMarkdown形式で記事の内容を入力するテキストエリアがあり、右側にはプレビューエリアがあります（Qiitaの下書き画面と同様に、左側での編集がリアルタイムで右側のプレビューに反映されます）。
<br>

  - **生成AIへの問い合わせ機能（アドバイスを求める）**
  ![after1.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3832049/bd217e14-8c6d-ed5c-7e03-43ab6ebefac5.gif)
  「生成AIに聞く」ボタンを押すとモーダルが起動し、ユーザーはAIへの問い合わせ内容を選択します。
  「記事のアドバイスを求める」を選択し、「送信する」ボタンを押すと、生成AIに対して記事のタイトル、タグ、本文の内容についてのアドバイスをリクエストし、画面下部のエリアに生成AIの回答が表示されます（生成AIのモデルはGemini API（Gemini 1.5 Flash）を使用しています）。
  <br>

  - **投稿予定の記事の内容と生成AIの回答**
    - 投稿予定の記事
  ![FireShot Capture 005 - Clear Draft - localhost 2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3832049/f08d5d12-4c27-a996-f3ae-2fe807b1b80d.png)
    - 生成AIの回答
![FireShot Capture 005 - Clear Draft - localhost.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3832049/0aaa63e5-101d-48ca-8b2f-119a029d109c.png)
AIの回答にはあらかじめ用意された以下のプロンプトに従ってMarkDown形式で生成されます。
<details><summary>生成AIへのプロンプトテンプレート</summary>

```
あなたはIT分野におけるプロのWebライターです。
Qiitaというサイトへの技術記事を投稿する予定で、以下の内容で記事を投稿することを検討しています。
投稿予定の記事についてのアドバイスを生成してください。アドバイスの内容は、タイトルの候補、記事につけるべきタグの候補、修正すべき内容、全体的なアドバイス、その他にしてください。
また、項目ごとに1行の空白行を入れてください。
なお、出力はMarkDown形式で出力してください。
◇タイトル
[[タイトル]]
◇本文
[[記事本文]]
```
</details>
<br>
  
  - **生成AIへの問い合わせ機能（その他問い合わせ）**
  ![after1.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3832049/60118f63-6c9a-f235-0275-aa7ff27fb670.gif)
    - 生成AIの回答
    ![スクリーンショット 2024-09-04 20.50.48（2）.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3832049/d619192a-28a7-f926-326c-ceabd519871f.png)
  生成AIへの問い合わせモーダルで「その他問い合わせ」を選択すると、AIに対して問い合わせる内容を設定するテキストボックスが表示されます。その後、ユーザーが問い合わせ内容を入力し、「送信する」ボタンを押すと、生成AIへのリクエストが送信されます。上の例では「表形式」と指定していますが、生成AIの回答はMarkdown形式で出力され、アプリ側でMarkdownを整形して表示するため、回答欄には見やすく表示されます。
  <details><summary>生成AIへのプロンプトテンプレート</summary>

```
以下の質問について回答してください。
[[問い合わせ内容]]
なお、出力はMarkDown形式で出力してください。
```
</details>

<br>

  - **記事保存機能**
  ![after1.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3832049/5f144601-eff0-7f51-4df1-7431982ebe7d.gif)
「保存する」ボタンを押すと、アプリ内のDB（Supabase）にデータが保存されます。
保存される項目は「タイトル、タグ、本文、生成AIの回答内容」で、後述の一覧画面に表示されます。一覧画面から「編集」ボタンを押すと、保存した記事の編集が可能となります。
<br>

  - **記事投稿機能**
  ![after1.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3832049/26cf8b50-b19d-a0ba-b5b9-41c27fd48088.gif)
「投稿する」ボタンを押下するとモーダルが表示され、ユーザーは公開範囲を選択します（「全体に公開」/「限定共有」）。その後、「投稿する」ボタンを押下すると、Qiita APIを利用した投稿処理が実行され、投稿先の記事のページが自動で表示されます。
上で投稿したサンプル記事は<a href="https://qiita.com/Kei-dev-1213/items/bc93ad9b6ff60208aaa0">こちら</a>
<br/>

- **記事一覧画面**
![スクリーンショット 2024-09-04 20.53.44（2）.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3832049/f2470402-366f-a5f0-b700-7484f8bcd2e9.png)
アプリ内で保存している記事の一覧画面です。前述の記事編集画面で記事を保存した場合、こちらの一覧画面に表示されます。右上の「未投稿の下書きのみ表示」スイッチを切り替えると、未投稿/全記事の表示切替を行うことが出来ます。また、本画面から記事の新規登録・記事の編集画面への遷移、及び記事の削除を行うことが出来ます。
<br>

  - **新規記事作成機能**
  ![after1.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3832049/fd11809c-4db9-b6b6-00ee-255d3b4d8c39.gif)
  一覧画面右上の「新規登録」ボタンを押下すると、前述の記事編集画面に遷移します（新規登録なので入力項目は全て未入力）。編集画面にて「保存する」ボタンを押下して一覧画面に遷移すると、保存した記事が一覧に表示されます。
<br>
  
  - **記事編集機能**
  ![after1.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3832049/e89606ab-1d95-b6a9-c6b8-c2d65948feb5.gif)
  一覧の「編集」ボタンを押下すると、対応する記事の編集画面に遷移します。
  前回保存ボタンを押下した状態で初期表示されます。
  <br>

  - **記事削除機能**
  ![after1.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3832049/bbff314a-ea69-8c50-4dab-8d8ab08f2cb3.gif)
  一覧の「削除」ボタンを押下すると、対応する記事の削除確認ダイアログが表示されます。「削除」ボタンを押下するとDBから削除され、一覧画面からも削除されます。
  <br>
  
- **QiitaAPIキー管理画面**
  ![after1.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3832049/714ebefe-8333-17d1-7b99-27b611336e6e.gif)
  Qiitaに投稿するために必要となるAPIキーを登録するための画面です。
  「登録」ボタンを押すことで、記事を投稿する際のAPIキーをBcryptを使ってソルト付きで暗号化し、DBに登録します。また、ローディング画面にはおしゃれに見えるように一工夫しています。詳細はぜひ↓の記事を参照してください。
  
https://qiita.com/Kei-dev-1213/items/d4df148ef5258b7e8e96

# 工夫したところと大変だったところ
本アプリの開発における工夫したところ・大変だったところを以下の通り記載します。

- **ユーザーインターフェースの工夫**  
  直感的で使いやすいインターフェースを目指し、特に記事編集画面や問い合わせモーダルのデザインにこだわりました。プレビュー機能や問い合わせ結果の表示など、ユーザーがストレスなく操作できるように工夫しています。
  
- **生成AIの回答時の表示方法**  
  生成AIからの回答を画面上に表示する際、APIからの戻り値をそのまま画面上に張り付けるのではなく、生成AIが回答しているように見せるため、1文字ずつ表示させています。これはstateの値をsetIntervalで1文字ずつ設定し直す方法で実現しています。こういった細かいところに気を配るだけでアプリがリッチに見えるため、ユーザー体験の質が大きく向上することが見込めますね。

- **Qiita APIとの連携**  
  Qiita APIを利用した直接投稿機能の実装は、APIの仕様理解やセキュリティ面での考慮が必要であり、特にAPIキーの管理には細心の注意を払いました。APIキーを安全に取り扱うために、Bcryptを用いた暗号化処理を実装し、データベースに登録する際のセキュリティを強化しました。

- **生成AIのプロンプト設計**  
  AIから有用なアドバイスを得るためには、適切なプロンプト設計が不可欠です。特に、技術記事に特化したアドバイスを得るために、プロンプトの内容を何度も調整し、最適化する必要がありました。これにより、ユーザーが求める具体的で実用的なアドバイスを提供できるようになりました。  

# 総括
今回の個人開発でまず感じたのは、 **<font color="Red">`実際に使われることを想定して作成するアプリは、完成時の達成感や自分自身の成長に繋がる`</font>** ということですね。当初はもっとシンプルなアプリにする予定でしたが、開発を進める中で「あれもあった方が良い」「これも追加した方が良い」と考えるうちに、結果的に多機能なアプリになりました。（世の中の多くの制作物がそういった形で進化していくのかもしれませんね。）

また、まさに「習うより慣れろ」という言葉通り、私は開発中におそらく100回以上のエラーにぶつかりました。しかし、そのたびに自身が成長していることを実感しました。エラー解決から学んだのは主にエラーメッセージの解読方法や、ブラウザの開発者ツールを活用したデバッグスキルですね。また、アプリの使いやすさ、つまりユーザー体験（UX）の重要性を学び、生成AIの回答を見やすく表示したり、記事編集画面の操作性を改善する工夫を重ね、直感的に使いやすいデザインを追求しました。さらに、Reactやその他の技術に対する理解も深まり、これが今後の開発にも大いに役立つ基礎となると感じています。`これらの経験は、ただ動画教材を見るだけでは得られない貴重なもの`でした（もちろん、動画教材を否定するつもりはありません）。

また、私の所属するコミュニティ「JISOU」では、インプットよりアウトプットを重視しており、私もその教えに従って学びを深めてきました。Reactに初めて触れたのは約1か月前で、最低限のインプットを行い、ひたすらアウトプットを繰り返すことでこのアプリを完成させることができました。やはり、アウトプットに勝る学習方法はないと改めて感じています。
本アプリの開発を通じて、Reactの知識だけでなく、実際に手を動かして学ぶことの重要性を痛感しました。

今後もこの経験を活かし、次なるアウトプットに挑戦していきたいと思います。
ここまでご覧いただき、ありがとうございました！

## JISOUのメンバー募集中🔥
プログラミングコーチングJISOUではメンバーを募集しています。
日本一のアウトプットコミュニティでキャリアアップしませんか？
気になる方はぜひHPからライン登録お願いします！👇

https://projisou.jp/