Next.jsで5分あれば作れるチャットAI

# 導入

ふとしたことからAIと会話するアプリを手元でサクッと作ることになったので備忘録。

# 使ったもの

- Next.js
- v0
- Cohere

# 手順

## Next.jsをインストール

shadcnのテンプレートを使いました。

https://github.com/shadcn-ui/next-template

```bash
npx create-next-app -e https://github.com/shadcn/next-template
```

## v0でページ作り

>AIとチャットできるサービスの画面を作成してください。 ChatGPTみたいな感じで。

shadcnのコンポーネントが足りないので必要なものはインストールする。

https://ui.shadcn.com/

```typescript
'use client'

import { useState } from 'react'
import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
import { ScrollArea } from "@/components/ui/scroll-area"
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"
import { Card, CardContent, CardFooter, CardHeader, CardTitle } from "@/components/ui/card"
import { MessageSquare, Send, Plus } from "lucide-react"

export default function Component() {
  const [messages, setMessages] = useState([
    { role: 'assistant', content: 'こんにちは！どのようなことでお手伝いできますか？' },
  ])
  const [input, setInput] = useState('')

  const handleSend = () => {
    if (input.trim()) {
      setMessages([...messages, { role: 'user', content: input }])
      // ここでAIの応答を生成する処理を追加します（この例では省略）
      setMessages(prev => [...prev, { role: 'assistant', content: 'AIの応答がここに表示されます。' }])
      setInput('')
    }
  }

  return (
    <div className="flex h-screen bg-gray-100">
      <main className="flex-1 flex flex-col">
        <Card className="flex-1 flex flex-col m-4 shadow-xl">
          <CardHeader>
            <CardTitle>AI チャットアシスタント</CardTitle>
          </CardHeader>
          <CardContent className="flex-1 overflow-hidden">
            <ScrollArea className="h-full">
              {messages.map((message, index) => (
                <div key={index} className={`flex items-start mb-4 ${message.role === 'user' ? 'justify-end' : ''}`}>
                  {message.role === 'assistant' && (
                    <Avatar className="mr-2">
                      <AvatarImage src="/placeholder.svg?height=40&width=40" alt="AI" />
                      <AvatarFallback><MessageSquare className="h-5 w-5" /></AvatarFallback>
                    </Avatar>
                  )}
                  <div className={`rounded-lg p-3 max-w-[80%] ${message.role === 'user' ? 'bg-blue-500 text-white' : 'bg-gray-200'}`}>
                    {message.content}
                  </div>
                  {message.role === 'user' && (
                    <Avatar className="ml-2">
                      <AvatarImage src="/placeholder.svg?height=40&width=40" alt="User" />
                      <AvatarFallback>U</AvatarFallback>
                    </Avatar>
                  )}
                </div>
              ))}
            </ScrollArea>
          </CardContent>
          <CardFooter>
            <form onSubmit={(e) => { e.preventDefault(); handleSend(); }} className="flex w-full items-center space-x-2">
              <Input
                type="text"
                placeholder="メッセージを入力..."
                value={input}
                onChange={(e) => setInput(e.target.value)}
              />
              <Button type="submit" size="icon">
                <Send className="h-4 w-4" />
                <span className="sr-only">送信</span>
              </Button>
            </form>
          </CardFooter>
        </Card>
      </main>
    </div>
  )
}
```

## CohereのAPIを利用できるようにする

https://docs.cohere.com/reference/about#typescript

```bash
npm i -s cohere-ai
```

tokenはここから取れます。

https://dashboard.cohere.com/api-keys

```diff_typescript
略

import { Card, CardContent, CardFooter, CardHeader, CardTitle } from "@/components/ui/card"
import { MessageSquare, Send, Plus } from "lucide-react"
+ const { CohereClientV2 } = require('cohere-ai');
+ const cohere = new CohereClientV2({
+   token: '[tokenを入れてね]',
+ });

export default function Component() {
  const [messages, setMessages] = useState([
    { role: 'assistant', content: 'こんにちは！どのようなことでお手伝いできますか？' },
  ])
  const [input, setInput] = useState('')

+  const handleSend = async () => {
    if (input.trim()) {
      setMessages([...messages, { role: 'user', content: input }])
      
+      const response = await cohere.chat({
+        model: 'command-r-plus',
+        messages: [
+          {
+            role: 'user',
+            content: input,
+          },
+        ],
+      });

+      setMessages(prev => [...prev, { role: 'assistant', content: response.message.content[0].text }])
      setInput('')
    }
  }

  return 略
}
```

できた(あまりに荒いもgif...)
![ai-chat.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2278656/183789b9-50ea-9c4d-8919-a6675b2a68b4.gif)

会話ができるようにしたいなら過去のやり取りを一緒に渡したり、用途を絞りたいならプロンプトを仕込めばいいと思います。
その他UIの調整などをして終わり。