AIでLINE返信サジェスト【企画書】

# プロジェクト概要
このプロジェクトは、LINEのようなメッセージアプリにおいて、AIが返信候補を提示してくれるサービスです。接客業などで「きちんとした文章を短時間で考えたい」という要望に応えるため設計されています。特に、GPT APIを利用し、過去の会話履歴を参照しながら適切な返信候補を表示する仕組みを導入しています。

:::note
CAMPFIREを通じて資金調達を計画していましたが、現在は募集を終了しました。
https://camp-fire.jp/projects/790373/view
:::

# システムの一連のフロー
1. LINE連携設定
    - ユーザーがLINEアプリを通じて、サービスと連携するためにログイン認証を行います
2. LINEメッセージの受信
    - LINEに受信したメッセージを、LINE APIを通じてアプリケーションサーバーに送信します
    - アプリケーションサーバーはメッセージ内容を解析し、どのような返信が適切か判断するために次のステップへ進みます
3. OpenAI APIの呼び出し
    - 受信したメッセージを元にOpenAI APIへリクエストを送信します
    - OpenAIは、メッセージの文脈を考慮して複数の返信候補（サジェスト）を生成し、それをアプリケーションサーバーに返します
4. サジェストの表示
    - OpenAI APIから受け取った複数のサジェストを表示します
    - 画面上に、サジェストされた返信候補が縦並びで表示され、ユーザーが確認できるようになります
5. 返信内容のコピー
    - ユーザーが表示されたサジェストの中から、返信したい内容を選択します
    - 選択された返信は、クリップボードにコピーされます
        - LINE APIではメッセージ送信はBotからしかできないので、コピーで留めています

## インフラ構成図
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/411120/e4d6de94-15b5-00b0-b64b-1eef2e99ca06.png)

# システム構成
## LINE連携
ユーザーがLINEアプリを通じて、サービスと連携するためにログイン認証を行います。LINE OAuthを利用したユーザー認証フローを導入します。

### フロントエンド
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/411120/3c957822-034f-9058-dafa-003feb39df64.png)

<details><summary>コード</summary>

```html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>LINE Login</title>
    <style>
        body {
            font-family: Arial, sans-serif;
            background-color: #f0f0f0;
            text-align: center;
            margin: 0;
            padding: 0;
        }

        .container {
            margin-top: 100px;
        }

        .line-login-btn {
            background-color: #00c300;
            color: white;
            padding: 15px 30px;
            font-size: 18px;
            border: none;
            border-radius: 5px;
            cursor: pointer;
            text-decoration: none;
            display: inline-block;
        }

        .line-login-btn:hover {
            background-color: #00a300;
        }
    </style>
</head>
<body>
    <div class="container">
        <h1>LINEアカウントを連携しましょう</h1>
        <p>以下のボタンをクリックして、LINEアカウントを連携してください。</p>
        <a href="https://access.line.me/oauth2/v2.1/authorize?response_type=code&client_id=YOUR_CLIENT_ID&redirect_uri=YOUR_REDIRECT_URI&state=YOUR_STATE&scope=profile%20openid%20email&bot_prompt=normal" class="line-login-btn">
            LINEでログイン
        </a>
    </div>
</body>
</html>
```

- `YOUR_CLIENT_ID`: LINE Developersで発行されたClient IDを設定します
- `YOUR_REDIRECT_URI`: 認証後にユーザーがリダイレクトされるURLを設定します。このURLには、後ほど説明するサーバー側の認証処理を行います
- `state`: セキュリティのための一意なトークン（CSRF攻撃防止用）です

</details>

## バックエンド
LINE APIでユーザーが認証を完了すると、認証コードがredirect_uriに送られてきます。このコードを使ってアクセストークンを取得します。

<details><summary>コード</summary>

```python
@app.route('/auth/callback')
def auth_callback():
    code = request.args.get('code')
    state = request.args.get('state')
    
    # 認証コードを使ってアクセストークンを取得
    token_url = "https://api.line.me/oauth2/v2.1/token"
    headers = {
        'Content-Type': 'application/x-www-form-urlencoded'
    }
    data = {
        'grant_type': 'authorization_code',
        'code': code,
        'redirect_uri': REDIRECT_URI,
        'client_id': LINE_CLIENT_ID,
        'client_secret': LINE_CLIENT_SECRET
    }

    response = requests.post(token_url, headers=headers, data=data)
    token_data = response.json()

    # アクセストークンが正常に取得できたか確認
    if 'access_token' in token_data:
        access_token = token_data['access_token']
        
        # アクセストークンを使ってユーザープロフィールを取得
        profile_url = "https://api.line.me/v2/profile"
        profile_headers = {
            'Authorization': f'Bearer {access_token}'
        }
        profile_response = requests.get(profile_url, headers=profile_headers)
        profile_data = profile_response.json()
        
        # ユーザー情報をセッションに保存するなどの処理
        session['user_profile'] = profile_data
        return f"LINEユーザー {profile_data['displayName']} と連携しました！"
    
    return "認証に失敗しました。"

```
</details>

### 詳細
1. LINEでログイン
    - エンドユーザーがLINEログインボタンをクリックすると、LINEのOAuth 2.0認証ページに遷移します
    - 認証後、LINEはredirect_uriに認証コードを付加してリダイレクトします
2. 認証コードの受け取り
    - バックエンドの/callbackエンドポイントで、この認証コードを受け取ります
3. アクセストークンの取得
    - 取得した認証コードを使い、LINE APIにPOSTリクエストを送りアクセストークンを取得します
4. ユーザープロフィールの取得
    - アクセストークンを使用して、LINEのプロフィール情報（ユーザーID、名前、アイコンなど）を取得します

## LINEメッセージの受信
LINEに受信したメッセージを、LINE APIを通じてアプリケーションサーバーに送信します

### バックエンド

<details><summary>コード</summary>

```py
@app.route("/webhook", methods=['POST'])
def callback():
    body = request.json
    events = body['events']
    
    # メッセージ受信イベントを処理
    for event in events:
        if event['type'] == 'message' and event['message']['type'] == 'text':
            user_message = event['message']['text']
```
</details>

## OpenAI APIの呼び出し
受信したメッセージを元にOpenAI APIへリクエストを送信します

### バックエンド

<details><summary>コード</summary>

```python
def get_openai_suggestions(message):
    openai_api_url = "https://api.openai.com/v1/completions"
    headers = {"Authorization": f"Bearer {YOUR_OPENAI_API_KEY}"}
    data = {
        "model": "gpt-3.5-turbo",
        "prompt": f"ユーザーのメッセージ: {message}\n返信候補を3つ提案してください。",
        "max_tokens": 100,
        "n": 3
    }
    response = requests.post(openai_api_url, headers=headers, json=data)
    suggestions = response.json()["choices"]
    return [suggestion["text"].strip() for suggestion in suggestions]
```
</details>

## サジェストの表示
OpenAI APIから受け取った複数のサジェストを表示します

### フロントエンド

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/411120/64a85a26-8bd4-1296-03a2-02b103caf4f1.png)

<details><summary>コード</summary>

```html
<script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/4.0.0/socket.io.min.js"></script>
<script>
    const socket = io("http://localhost:5000");

    socket.on("suggestions", function(data) {
        displaySuggestions(data.suggestions);
    });

    function displaySuggestions(suggestions) {
        const suggestionsContainer = document.getElementById("suggestions");
        suggestionsContainer.innerHTML = ''; // 既存のサジェストをクリア

        suggestions.forEach((suggestion, index) => {
            const suggestionItem = document.createElement("div");
            suggestionItem.classList.add("suggestion-item");
            suggestionItem.textContent = `候補 ${index + 1}: ${suggestion}`;
            suggestionItem.onclick = function() {
                copyToClipboard(suggestion); // サジェストをクリックしたらコピー
            };
            suggestionsContainer.appendChild(suggestionItem);
        });
    }
</script>

```

</details>

### バックエンド

<details><summary>コード</summary>

```python
def send_to_frontend(suggestions):
    # WebSocketを通じてフロントエンドにサジェストを送信
    socketio.emit('suggestions', {'suggestions': suggestions})
```

</details>

## 返信内容のコピー
ユーザーが表示されたサジェストの中から、返信したい内容を選択します

<details><summary>クリックして展開</summary>

```js
function copyToClipboard(text) {
    navigator.clipboard.writeText(text).then(() => {
        showCopiedMessage();
    }).catch(err => {
        console.error('Failed to copy: ', err);
    });
}
```

</details>

# セキュリティ面について
セキュリティを強化するため、以下の対策を行います

- SSL/TLSの導入: すべての通信は暗号化され、ユーザーのプライバシーが保護されます
- 認証: LINE APIとの通信では、適切な認証トークンを使用し、外部からの不正アクセスを防ぎます
- データ保護: 個人情報は最小限の保存にとどめ、会話履歴などのデータも匿名化した上で処理されます

# クラウドファンディングの目的
このプロジェクトは、サービスの実現に向けて必要な資金を調達するためにクラウドファンディングを利用します。資金は主に以下の目的に使われます

- GPT APIの利用料
- 開発チームの人件費
- サーバーやインフラの費用

# 最後に
このプロジェクトは、より多くの人にAIを身近に感じてもらい、日々のコミュニケーションをサポートできるサービスを提供したいという思いからスタートしました。