こんにちは
株式会社クラスアクト インフラストラクチャ事業部の大塚です。
Viteを使ったReact環境にGemini APIを連携してチャットアプリを作っていきたいと思います。
※当方インフラエンジニアです。フロントエンド関係は勉強中です。なので詳しいことは書けません。自分用のメモ的なサムシングです。
※細かいことはこれから勉強です。
GitHub
最終的なフォルダ構成
test@DAIV-SHOLAND:/mnt/c/Users/ohtsu/Documents/react_dev/react-chat-app$ tree -L 2
.
├── README.md
├── config
│ ├── config-sample.js
│ └── config.js
├── eslint.config.js
├── index.html
├── node_modules
│ ├── @ampproject
│ ├── @babel
│ ├── @esbuild
│ ├── @eslint
│ ├── @eslint-community
│ ├── @google
│ ├── @humanfs
│ ├── @humanwhocodes
│ ├── @jridgewell
│ ├── @lit
│ ├── @lit-labs
│ ├── @material
│ ├── @rolldown
│ ├── @rollup
│ ├── @types
│ ├── @vitejs
│ ├── acorn
│ ├── acorn-jsx
│ ├── ajv
│ ├── ansi-styles
│ ├── argparse
│ ├── balanced-match
│ ├── brace-expansion
│ ├── browserslist
│ ├── callsites
│ ├── caniuse-lite
│ ├── chalk
│ ├── color-convert
│ ├── color-name
│ ├── concat-map
│ ├── convert-source-map
│ ├── cross-spawn
│ ├── csstype
│ ├── debug
│ ├── deep-is
│ ├── electron-to-chromium
│ ├── esbuild
│ ├── escalade
│ ├── escape-string-regexp
│ ├── eslint
│ ├── eslint-plugin-react-hooks
│ ├── eslint-plugin-react-refresh
│ ├── eslint-scope
│ ├── eslint-visitor-keys
│ ├── espree
│ ├── esquery
│ ├── esrecurse
│ ├── estraverse
│ ├── esutils
│ ├── fast-deep-equal
│ ├── fast-json-stable-stringify
│ ├── fast-levenshtein
│ ├── fdir
│ ├── file-entry-cache
│ ├── find-up
│ ├── flat-cache
│ ├── flatted
│ ├── gensync
│ ├── glob-parent
│ ├── globals
│ ├── has-flag
│ ├── ignore
│ ├── import-fresh
│ ├── imurmurhash
│ ├── is-extglob
│ ├── is-glob
│ ├── isexe
│ ├── js-tokens
│ ├── js-yaml
│ ├── jsesc
│ ├── json-buffer
│ ├── json-schema-traverse
│ ├── json-stable-stringify-without-jsonify
│ ├── json5
│ ├── keyv
│ ├── levn
│ ├── lit
│ ├── lit-element
│ ├── lit-html
│ ├── locate-path
│ ├── lodash.merge
│ ├── lru-cache
│ ├── minimatch
│ ├── ms
│ ├── nanoid
│ ├── natural-compare
│ ├── node-releases
│ ├── optionator
│ ├── p-limit
│ ├── p-locate
│ ├── parent-module
│ ├── path-exists
│ ├── path-key
│ ├── picocolors
│ ├── picomatch
│ ├── postcss
│ ├── prelude-ls
│ ├── punycode
│ ├── react
│ ├── react-dom
│ ├── react-refresh
│ ├── resolve-from
│ ├── rollup
│ ├── scheduler
│ ├── semver
│ ├── shebang-command
│ ├── shebang-regex
│ ├── source-map-js
│ ├── strip-json-comments
│ ├── supports-color
│ ├── tinyglobby
│ ├── tslib
│ ├── type-check
│ ├── update-browserslist-db
│ ├── uri-js
│ ├── vite
│ ├── which
│ ├── word-wrap
│ ├── yallist
│ └── yocto-queue
├── package-lock.json
├── package.json
├── public
│ └── vite.svg
├── src
│ ├── App.css
│ ├── App.jsx
│ ├── assets
│ ├── gemini.js
│ ├── index.css
│ ├── main.jsx
│ └── org
└── vite.config.js
React+Vite環境の用意
npm create vite@latest .
npm install
Gemini APIの発行
これにアクセス。
右上にGet API Keyという表示があるのでこれを押下する。
APIキーを作成ボタンがあるので、これを押下する。
"gemini-2.0-flash:generateContent"と記載されており、これは後程使うのでメモっておく。
React関連のコード修正
必要なものの追加インストール
C:\Users\ohtsu\Documents\react_dev\react-chat-app>npm install @material/web
added 53 packages, and audited 206 packages in 14s
33 packages are looking for funding
run `npm fund` for details
found 0 vulnerabilities
C:\Users\ohtsu\Documents\react_dev\react-chat-app>npm install @google/generative-ai
added 1 package, and audited 207 packages in 2s
33 packages are looking for funding
run `npm fund` for details
found 0 vulnerabilities
package.jsonを念のため確認する。
{
"name": "chat",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"lint": "eslint .",
"preview": "vite preview"
},
"dependencies": {
"@google/generative-ai": "^0.24.1",
"@material/web": "^2.3.0",
"react": "^19.1.0",
"react-dom": "^19.1.0"
},
"devDependencies": {
"@eslint/js": "^9.30.1",
"@types/react": "^19.1.8",
"@types/react-dom": "^19.1.6",
"@vitejs/plugin-react": "^4.6.0",
"eslint": "^9.30.1",
"eslint-plugin-react-hooks": "^5.2.0",
"eslint-plugin-react-refresh": "^0.4.20",
"globals": "^16.3.0",
"vite": "^7.0.4"
}
}
コード修正
App.jsx
import './App.css';
import React, { useRef, useState } from 'react';
import '@material/web/button/filled-button';
import '@material/web/textfield/filled-text-field';
import { geminiRun } from './gemini'; // gemini.jsからインポート
function App() {
const inputRef = useRef(null);
const [messages, setMessages] = useState([]); // メッセージのステートを追加
const onButtonClick = async () => {
if (!inputRef.current) { return; }
const inputText = inputRef.current.value;
const response = await geminiRun(inputText);
// 新しいメッセージを追加
setMessages((prevMessages) => [
...prevMessages,
{ text: inputText, type: 'user' }, // ユーザーのメッセージ
{ text: response, type: 'gemini' } // Geminiの応答
]);
// 入力フィールドをクリア
inputRef.current.value = '';
}
return (
<div className="chat-container">
<div className="chat-window">
<div className="messages">
{messages.map((msg, index) => (
<div key={index} className={`message ${msg.type}`}>
{msg.text}
</div>
))}
</div>
<div className="input-container">
<md-filled-text-field
id="input"
type="textarea"
rows="5"
ref={inputRef}
className="message-input"
/>
<md-filled-button
type="button"
id="button"
className="send-button"
onClick={onButtonClick}
>
Send
</md-filled-button>
</div>
</div>
</div>
);
}
export default App;
App.css
/* App.css */
body {
margin: 0;
display: flex;
flex-direction: column;
justify-content: flex-end;
min-height: 100vh;
background-color: #f0f0f0;
}
.chat-container {
width: 100%;
max-width: 800px;
min-width: 800px; /* 文字が入力されていなくても800pxの横幅を持たせる */
border: 1px solid #ccc;
border-radius: 8px;
overflow: hidden;
background-color: white;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
margin-bottom: 30px;
}
.chat-window {
display: flex;
flex-direction: column;
height: 100%;
}
.messages {
flex: 1;
padding: 10px;
overflow-y: auto; /* スクロール機能の追加 */
border-bottom: 1px solid #ccc;
max-height: 400px; /* 高さを固定 */
}
.message {
padding: 8px;
margin: 5px 0;
border-radius: 5px;
background-color: #e1ffc7;
}
.message.user {
background-color: #e1ffc7; /* ユーザーのメッセージの背景色 */
}
.message.gemini {
background-color: #d1e7ff; /* Geminiのメッセージの背景色 */
}
.input-container {
display: flex;
padding: 10px;
background-color: #f9f9f9;
}
.message-input {
flex: 1;
padding: 10px;
border: 1px solid #ccc;
border-radius: 5px;
margin-right: 10px;
resize: none; /* サイズ変更を無効にする */
overflow: auto; /* オーバーフロー時にスクロールバーを表示 */
}
.send-button {
padding: 10px 15px;
border: none;
border-radius: 5px;
background-color: #007bff;
color: white;
cursor: pointer;
}
.send-button:hover {
background-color: #0056b3;
}
@media (max-width: 800px) {
.chat-container {
max-width: 100%; /* 小さい画面では100%の幅にする */
min-width: 400px; /* 文字が入力されていなくても400pxの横幅を持たせる */
}
}
gemini.js
srcディレクトリ配下にgemini.jsを作成して中身を以下とする
// src/gemini.js
import { GoogleGenerativeAI } from "@google/generative-ai";
import { API_KEY } from "../config/config.js";
const genAI = new GoogleGenerativeAI(API_KEY);
export async function geminiRun(prompt) {
const model = genAI.getGenerativeModel({ model: "gemini-2.0-flash" }); // モデル名はAPIキー作成した時に確認したもの
const result = await model.generateContent(prompt);
const response = await result.response;
const text = await response.text(); // ここでawaitを使用してテキストを取得
console.log(text);
return text;
}
config.js
Gemini APIのデータをコンフィグファイルに記載する
../config/config.js
export const API_KEY = 'YOUR_API_KEY_HERE'; // ここに実際のAPIキーを入力
.gitignore
GitHubでコードを管理する場合、.gitignoreを以下のように記述する
config/config.js
アプリ起動
C:\Users\ohtsu\Documents\react_dev\react-chat-app>npm run dev
> chat@0.0.0 dev
> vite
VITE v7.0.5 ready in 157 ms
➜ Local: http://localhost:5173/
➜ Network: use --host to expose
➜ press h + enter to show help