Skip to content

Commit 0597dfa

Browse files
authored
Merge pull request #7 from ut-code/chat
チャットフォームのクライアント側とサーバー側APIを連携
2 parents c979a92 + 29d0e0a commit 0597dfa

File tree

2 files changed

+129
-85
lines changed

2 files changed

+129
-85
lines changed

app/[docs_id]/chatForm.tsx

Lines changed: 125 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -1,112 +1,160 @@
11
"use client";
22

3-
import { hello } from "./chatServer";
3+
import { useState, FormEvent } from "react";
44

5-
export function ChatForm() {return (
5+
interface ChatApiResponse {
6+
response: string;
7+
}
8+
9+
export function ChatForm() {
10+
const [inputValue, setInputValue] = useState("");
11+
const [response, setResponse] = useState("");
12+
const [isLoading, setIsLoading] = useState(false);
13+
14+
const handleSubmit = async (e: FormEvent<HTMLFormElement>) => {
15+
e.preventDefault();
16+
setIsLoading(true);
17+
setResponse("");
18+
19+
try {
20+
const res = await fetch("/api/chat", {
21+
method: "POST",
22+
headers: {
23+
"Content-Type": "application/json",
24+
},
25+
body: JSON.stringify({ message: inputValue }),
26+
});
27+
28+
const data = (await res.json()) as ChatApiResponse;
29+
if (!res.ok) {
30+
throw new Error(data.response || "エラーが発生しました。");
31+
}
32+
setResponse(data.response);
33+
} catch (error: unknown) {
34+
if (error instanceof Error) {
35+
setResponse(`エラー: ${error.message}`);
36+
} else {
37+
setResponse(`エラー: ${String(error)}`);
38+
}
39+
} finally {
40+
setIsLoading(false);
41+
}
42+
};
43+
return (
644
<>
7-
<style jsx>{`
45+
<style jsx>{`
846
/* 簡単なCSSで見た目を整える(オプション) */
947
.form-container {
10-
background-color: white;
11-
border-radius: 10px;
12-
box-shadow: 0 4px 8px rgba(67, 204, 216, 0.86);
13-
padding: 20px;
14-
width: 90%;
15-
max-width: 1000px;
16-
display: flex;
17-
flex-direction: column;
48+
background-color: white;
49+
border-radius: 10px;
50+
box-shadow: 0 4px 8px rgba(67, 204, 216, 0.86);
51+
padding: 20px;
52+
width: 90%;
53+
max-width: 1000px;
54+
display: flex;
55+
flex-direction: column;
1856
}
1957
.input-area {
20-
border: 1px solid #ccc;
21-
border-radius: 8px;
22-
padding: 5px 15 px;
23-
margin-bottom: 15px;
24-
min-height: 150px; /* 入力欄の高さ */
25-
display: flex;
58+
border: 1px solid #ccc;
59+
border-radius: 8px;
60+
padding: 5px 15 px;
61+
margin-bottom: 15px;
62+
min-height: 150px; /* 入力欄の高さ */
63+
display: flex;
2664
}
2765
.text-input {
28-
border: none;
29-
outline: none;
30-
flex-grow: 1;
31-
font-size: 16px;
32-
resize: none; /* テキストエリアのリサイズを無効化 */
33-
overflow: auto;
34-
padding: 10px;
66+
border: none;
67+
outline: none;
68+
flex-grow: 1;
69+
font-size: 16px;
70+
resize: none; /* テキストエリアのリサイズを無効化 */
71+
overflow: auto;
72+
padding: 10px;
3573
}
3674
.controls {
37-
display: flex;
38-
align-items: center;
39-
justify-content: space-between;
75+
display: flex;
76+
align-items: center;
77+
justify-content: space-between;
4078
}
4179
.left-icons button {
42-
background: none;
43-
border: none;
44-
font-size: 24px;
45-
cursor: pointer;
46-
color: #555;
47-
margin-right: 15px;
48-
padding: 5px;
80+
background: none;
81+
border: none;
82+
font-size: 24px;
83+
cursor: pointer;
84+
color: #555;
85+
margin-right: 15px;
86+
padding: 5px;
4987
}
5088
.left-icons button:hover {
51-
color: #000;
89+
color: #000;
5290
}
5391
.left-icons span {
54-
font-size: 14px;
55-
vertical-align: middle;
56-
margin-left: 5px;
57-
color: #555;
92+
font-size: 14px;
93+
vertical-align: middle;
94+
margin-left: 5px;
95+
color: #555;
5896
}
5997
.right-controls {
60-
display: flex;
61-
align-items: center;
98+
display: flex;
99+
align-items: center;
62100
}
63101
.voice-icon button {
64-
background: none;
65-
border: none;
66-
font-size: 24px;
67-
cursor: pointer;
68-
color: #555;
69-
margin-right: 15px;
70-
padding: 5px;
102+
background: none;
103+
border: none;
104+
font-size: 24px;
105+
cursor: pointer;
106+
color: #555;
107+
margin-right: 15px;
108+
padding: 5px;
71109
}
72110
.voice-icon button:hover {
73-
color: #000;
111+
color: #000;
74112
}
75113
.send-button {
76-
background-color: #007bff; /* 青色の送信ボタン */
77-
color: white;
78-
border: none;
79-
border-radius: 50%; /* 丸いボタン */
80-
width: 40px;
81-
height: 40px;
82-
display: flex;
83-
justify-content: center;
84-
align-items: center;
85-
font-size: 20px;
86-
cursor: pointer;
87-
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
88-
transition: background-color 0.3s ease;
114+
background-color: #007bff; /* 青色の送信ボタン */
115+
color: white;
116+
border: none;
117+
border-radius: 50%; /* 丸いボタン */
118+
width: 40px;
119+
height: 40px;
120+
display: flex;
121+
justify-content: center;
122+
align-items: center;
123+
font-size: 20px;
124+
cursor: pointer;
125+
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2);
126+
transition: background-color 0.3s ease;
89127
}
90128
.send-button:hover {
91-
background-color: #0056b3;
129+
background-color: #0056b3;
92130
}
93-
`}</style>
131+
`}</style>
94132

95-
<div className="form-container">
133+
<form className="form-container" onSubmit={handleSubmit}>
96134
<div className="input-area">
97-
<textarea className="text-input" placeholder="質問を入力してください"></textarea>
135+
<textarea
136+
className="text-input"
137+
placeholder="質問を入力してください"
138+
value={inputValue}
139+
onChange={(e) => setInputValue(e.target.value)}
140+
disabled={isLoading}
141+
></textarea>
98142
</div>
99-
100143
<div className="controls">
101-
<div className="left-icons">
102-
103-
</div>
104-
<div className="right-controls">
105-
<button type="submit" className="send-button" title="送信">
106-
<span className="icon"></span>
107-
</button>
108-
</div>
144+
<div className="left-icons"></div>
145+
<div className="right-controls">
146+
<button
147+
type="submit"
148+
className="send-button"
149+
title="送信"
150+
disabled={isLoading}
151+
>
152+
<span className="icon"></span>
153+
</button>
154+
</div>
109155
</div>
110-
</div>
156+
</form>
157+
{response && <div className="response-container">{response}</div>}
111158
</>
112-
)}
159+
);
160+
}
Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,11 @@
1-
import { NextResponse } from 'next/server';
2-
import { GoogleGenerativeAI } from '@google/generative-ai';
3-
1+
import { NextResponse } from "next/server";
2+
import { GoogleGenerativeAI } from "@google/generative-ai";
43

54
const genAI = new GoogleGenerativeAI(process.env.API_KEY);
65

76
export async function POST(request) {
87
const { message } = await request.json();
98

10-
119
if (!message) {
1210
return NextResponse.json(
1311
{ error: "メッセージがありません。" },
@@ -17,19 +15,17 @@ export async function POST(request) {
1715

1816
try {
1917
const model = genAI.getGenerativeModel({ model: "gemini-1.5-flash" });
20-
18+
2119
const result = await model.generateContent(message);
2220
const response = result.response;
2321
const text = response.text();
2422

2523
return NextResponse.json({ response: text });
26-
2724
} catch (e) {
28-
2925
console.error("Error:", e);
3026
return NextResponse.json(
3127
{ response: "エラーが発生しました。" },
3228
{ status: 500 }
3329
);
3430
}
35-
}
31+
}

0 commit comments

Comments
 (0)