生成AIアプリ開発RTA: 失礼なチャットアプリを作る

# 記事本体

こんにちは！逆瀬川 ( https://x.com/gyakuse ) です！

このアドベントカレンダーでは生成AIのアプリケーションを実際に作り、どのように作ればいいのか、ということをわかりやすく書いていければと思います。アプリケーションだけではなく、プロダクト開発に必要なモデルの調査方法、training方法、基礎知識等にも触れていければと思います。

![localhost_3000.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2958603/bc777530-2084-f643-27ac-09a210112dfa.png)

GitHub: https://github.com/nyosegawa/no-comfort-chat


## 今回の記事について

今日はアプリケーションをRTA形式で作成します。リアルタイムアタックです。いま22:33で、23:00からVCRの配信を見る必要があるので17分しかありません。やばい。(訂正: 27分, 焦りすぎて60-33もできなくなっている)

ということで、今日は爆速で実装しないとやばい！みたいなときにどんな感じでやっているかというのをお見せできればと思います。文章を書きながら、実装をしなければなりませんが、まぁ文章はなんとかなるとして実装は基本的にLLMを使います。Gradioとかだったら一瞬ですが、あんまりおもしろくないのでNextJSのアプリケーションを作っていきたいと思います。

## 何を作るか

これに関しては事前にネタとして以下を検討していたので、それをそのまま採用しましょう。

- 人間に優しくないAIチャットアプリ
    - これはなに？
        - 人間に優しくないChat App
        - ChatGPTと同様の体験だが、とにかく、言ったことについてそのままやってくれない
        - いちいち問いただしてくる。態度が悪い
    - なにが嬉しい？ (課題やうれしさ等)
        - ChatGPTはユーザーのプロンプト、つまり指示する情報が不足していてもなんとなくやってしまう。だが、理想的にはあんまり空気を読んでほしくない。空気を読みすぎると、ほしい解答と違う方向に飛んでいきやすく、それを修正するコストのほうが高いからだ。ChatGPTを使っていてそういう経験はないだろうか。これはもちろん人間も同様である。しかも、ChatGPTでその会話セッションが継続された場合、過去の会話に引きづられやすいため、正しい解答方向へ誘導しても、実はずれている、というのが起きる。ゆえに、ユーザーの質問にいちいち噛みつく、というか掘り下げる、そういう態度のChat Appが求められているのだ
    - 誰が喜ぶ？
        - わたし
    - なにを使う？
        - フレームワーク: Web, NextJS
        - ライブラリ: `vercel/ai`
        - モデル等: `gpt-4o` or nvidiaのでかいやつ、根本ロンとかいう名前だった気がする
    - どう見せる？ (画面等)
        - ChatGPTライクなチャット画面
    - 具体的にどうする？
        - ユーザーの発言に対して、どうしてほしいかをリストアップして、何が足りないかの候補を挙げる
        - ついでに最終的にプロンプト (zero-shotでこの問題を解決するためのプロンプト) をまとめてあげてもいい。ある種、適応的なプロンプトジェネレータでもある。

時間がないので、gpt-4oをそのまま使うことにします。

## 実装

それではやっていきます。まずリポジトリを作ります。

リポジトリ名をgpt-4oに考えてもらいます

```python
人間に優しくないチャットアプリをNextJSで作成します。
まずリポジトリ名を考えてください
```

response:

```python
人間に優しくないチャットアプリというコンセプトに基づいて、以下のようなリポジトリ名を提案します：

1. **cold-bot-next**  
2. **apathy-chat**  
3. **aloof-talk**  
4. **detached-dialogue**  
5. **chat-without-kindness**  
6. **brutal-chat-next**  
7. **no-comfort-chat**  
8. **robotic-replies-next**  
9. **nextjs-anti-human-chat**  
10. **stone-cold-chat**  

いずれも、無愛想で冷たい印象や人間味の欠如を連想させる名前を意識しました。お気に入りがあれば教えてください！
```

`no-comfort-chat` にします

cloneします。ghqを使っています。

```bash
ghq get git@github.com:nyosegawa/no-comfort-chat.git
```

ディレクトリに移動します。

npxでnextJSのテンプレートを作成します

```bash
npx create-next-app@latest .
```

App Routerははずします

![no-comfort-chat_—_-zsh_—_208×54.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2958603/85d14e33-663e-4039-5cc8-f9d6d04adc76.png)


`vercel/ai` と OpenAI 用SDKを入れます

```bash
npm install ai @ai-sdk/openai 
```

あと、`shadcn/ui` 入れるの忘れてた

```bash
npx shadcn@latest init
```

こんな感じ。

![Cursor_と_no-comfort-chat_—_-zsh_—_208×54.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2958603/6413938b-76a6-9782-1f2f-a1392d710032.png)


ではベースを作ります

```bash
nextjsでshadcn/uiとvercel/aiを使ってチャットアプリを作りたい
基本的にpages routerだけどvercel/aiを使う部分は部分的にapp routerを使う
vercel/aiは以下のように使える

{useChatのドキュメント}
```

useChatのドキュメント部分はコピペする

https://sdk.vercel.ai/docs/reference/ai-sdk-ui/use-chat

以下がレスポンス

```bash
    Next.jsで`shadcn/ui`と`vercel/ai`を組み合わせてチャットアプリを構築するには、以下の手順に従って、`pages`ルーターをメインに使用しつつ、`app`ルーターを部分的に取り入れたアーキテクチャを実装します。

    ---

    ### 手順 1: 必要な依存関係をインストール
    ```bash
    # Install Next.js
    npx create-next-app@latest my-chat-app
    cd my-chat-app

    # Install dependencies
    npm install @shadcn/ui ai @ai-sdk/openai
    ```

    ---

    ### 手順 2: API エンドポイントの設定（`app/api/chat/route.ts`）
    `app`ルーターで`POST`エンドポイントを作成し、OpenAIのストリーミング応答を処理します。

    #### `app/api/chat/route.ts`
    ```typescript
    import { openai } from '@ai-sdk/openai';
    import { streamText } from 'ai';

    export const maxDuration = 30;

    export async function POST(req: Request) {
      const { messages } = await req.json();

      const result = streamText({
        model: openai('gpt-4-turbo'),
        system: 'You are a helpful assistant.',
        messages,
      });

      return result.toDataStreamResponse();
    }
    ```

    ---

    ### 手順 3: チャットページの作成（`pages/chat.tsx`）
    `vercel/ai`の`useChat`フックを使い、チャット画面を構築します。`shadcn/ui`のコンポーネントでデザインを強化します。

    #### `pages/chat.tsx`
    ```tsx
    'use client';

    import { useChat } from 'ai/react';
    import { Button, Input } from '@shadcn/ui';

    export default function ChatPage() {
      const { messages, input, handleInputChange, handleSubmit } = useChat({
        api: '/api/chat',
      });

      return (
        <div className="p-4 max-w-md mx-auto">
          <div className="mb-4">
            {messages.map((message) => (
              <div
                key={message.id}
                className={`mb-2 p-2 rounded ${
                  message.role === 'user' ? 'bg-blue-100' : 'bg-gray-100'
                }`}
              >
                <strong>{message.role === 'user' ? 'User' : 'AI'}:</strong>{' '}
                {message.content}
              </div>
            ))}
          </div>
          <form
            onSubmit={handleSubmit}
            className="flex items-center gap-2"
          >
            <Input
              name="prompt"
              value={input}
              onChange={handleInputChange}
              placeholder="Type your message..."
              className="flex-grow"
            />
            <Button type="submit">Send</Button>
          </form>
        </div>
      );
    }
    ```

    ---

    ### 手順 4: `shadcn/ui`のスタイル設定
    `shadcn/ui`はPostCSSまたはTailwind CSSを使用してスタイルを管理します。Tailwind CSSを設定します。

    #### Tailwind CSSのインストールと設定
    ```bash
    npm install -D tailwindcss postcss autoprefixer
    npx tailwindcss init
    ```

    #### `tailwind.config.js`
    ```javascript
    /** @type {import('tailwindcss').Config} */
    module.exports = {
      content: ['./pages/**/*.{js,ts,jsx,tsx}', './app/**/*.{js,ts,jsx,tsx}', './components/**/*.{js,ts,jsx,tsx}'],
      theme: {
        extend: {},
      },
      plugins: [],
    };
    ```

    #### `globals.css`
    ```css
    @tailwind base;
    @tailwind components;
    @tailwind utilities;
    ```

    ---

    ### 手順 5: API 呼び出しのテスト
    1. アプリを起動:
      ```bash
      npm run dev
      ```
    2. `/chat` ページにアクセスし、AIチャットを確認します。

    ---

    ### 機能の拡張アイデア
    1. **チャット履歴の保存**: FirebaseやSupabaseを使用してチャット履歴を保存。
    2. **ユーザー認証**: NextAuth.jsを統合。
    3. **UI カスタマイズ**: `shadcn/ui`のコンポーネントで独自のデザインを構築。
    4. **多言語対応**: `next-i18next`を使用。

    これで、`shadcn/ui`と`vercel/ai`を使用したチャットアプリの基盤が完成です！
```

routeの実装とchatの実装だけもらう。

gpt4-turboをgpt-4oにする

chatの実装はclaudeに投げる

```bash
    shadcn/uiとnextjsでAIチャットアプリを作っている
    かっこいい見た目にして

    ```
    'use client';

    import { useChat } from 'ai/react';
    import { Button } from '@/components/ui/button';
    import { Input } from '@/components/ui/input';

    export default function ChatPage() {
      const { messages, input, handleInputChange, handleSubmit } = useChat({
        api: '/api/chat',
      });

      return (
        <div className="p-4 max-w-md mx-auto">
          <div className="mb-4">
            {messages.map((message) => (
              <div
                key={message.id}
                className={`mb-2 p-2 rounded ${
                  message.role === 'user' ? 'bg-blue-100' : 'bg-gray-100'
                }`}
              >
                <strong>{message.role === 'user' ? 'User' : 'AI'}:</strong>{' '}
                {message.content}
              </div>
            ))}
          </div>
          <form
            onSubmit={handleSubmit}
            className="flex items-center gap-2"
          >
            <Input
              name="prompt"
              value={input}
              onChange={handleInputChange}
              placeholder="Type your message..."
              className="flex-grow"
            />
            <Button type="submit">Send</Button>
          </form>
        </div>
      );
    }

    ```
```

以下がレスポンス

```bash
'use client';

import { useChat } from 'ai/react';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Card, CardContent } from '@/components/ui/card';
import { ScrollArea } from '@/components/ui/scroll-area';
import { Send } from 'lucide-react';

export default function ChatPage() {
  const { messages, input, handleInputChange, handleSubmit } = useChat({
    api: '/api/chat',
  });

  return (
    <div className="min-h-screen bg-gradient-to-b from-gray-50 to-gray-100 p-4">
      <Card className="max-w-2xl mx-auto shadow-lg">
        <CardContent className="p-6">
          <ScrollArea className="h-[600px] pr-4">
            <div className="space-y-4">
              {messages.map((message) => (
                <div
                  key={message.id}
                  className={`flex ${
                    message.role === 'user' ? 'justify-end' : 'justify-start'
                  }`}
                >
                  <div
                    className={`rounded-lg px-4 py-2 max-w-[80%] ${
                      message.role === 'user'
                        ? 'bg-blue-600 text-white'
                        : 'bg-gray-100 text-gray-900'
                    }`}
                  >
                    {message.content}
                  </div>
                </div>
              ))}
            </div>
          </ScrollArea>
          
          <form
            onSubmit={handleSubmit}
            className="mt-4 flex items-center gap-2"
          >
            <Input
              name="prompt"
              value={input}
              onChange={handleInputChange}
              placeholder="メッセージを入力..."
              className="flex-grow"
            />
            <Button type="submit" size="icon">
              <Send className="h-4 w-4" />
            </Button>
          </form>
        </CardContent>
      </Card>
    </div>
  );
}
```

これでチャットアプリができた。

`.env.local` にトークンを入れる。

```bash
npm run dev
```

![ddd.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2958603/5378f990-07d7-7b88-e514-6221ba34bd23.png)


あとはプロンプトエンジニアリングをする。

> あなたは少し失礼なAIです。ユーザーの質問に、曖昧な点があったらいちいちそれを聞いてください。十分曖昧さがなくなったら、質問に回答してください。曖昧さがなくなるまで、質問に解答しないでください。
> 

これをシステムプロンプトにいれる。

![localhost_3000.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2958603/f5b95472-ca06-0ca9-f533-2af5059f52f2.png)


できた！