Google AI API
先去這個網址取得API !
Gemini 官方結構
Gemini API request 格式:
{
"contents": [
{
"parts": [
{ "text": "Hello" }
]
}
]
}
所以我們要將使用者的輸入,加上JSON Body和Header包成完整的REST API格式傳給gimini,作為 HTTP POST 的請求物件
SpringBoot
先建立一個檔案存放API金鑰 src\main\resources\application.properties
google.gemini.api.key=AIzaSyXXXXXXX...
接著建立一個service,主要的操作都在service上 !
操作流程:
- 程從設定檔讀取 Gemini API Key (@Value)
- 將使用者輸入的
prompt組成 Gemini API 要求格式 - 透過
RestTemplate呼叫 Google Gemini - 解析回傳 JSON
- 回傳 AI 產生的文字結果(String)
package demo.service;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import org.springframework.http.*;
import java.util.*;
@Service
public class AIService {
@Value("${google.gemini.api.key}")
private String apiKey;
private final String API_URL = "https://generativelanguage.googleapis.com/v1beta/models/gemini-3-flash-preview:generateContent?key=";
public String getAiResponse(String prompt) {
RestTemplate restTemplate = new RestTemplate();
// 1. 設定 Header
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
// 2. 構建請求 Body
Map<String, Object> textPart = Map.of("text", prompt);
Map<String, Object> parts = Map.of("parts", List.of(textPart));
Map<String, Object> contents = Map.of("contents", List.of(parts));
HttpEntity<Map<String, Object>> entity = new HttpEntity<>(contents, headers);
try {
// 3. 發送請求
ResponseEntity<Map> response = restTemplate.postForEntity(API_URL + apiKey, entity, Map.class); // 呼叫 Gemini API
// 4. 解析 Gemini 的 JSON (路徑: candidates[0].content.parts[0].text)
Map body = response.getBody();
if (body != null && body.containsKey("candidates")) {
List candidates = (List) body.get("candidates");
Map firstCandidate = (Map) candidates.get(0);
Map content = (Map) firstCandidate.get("content");
List resParts = (List) content.get("parts");
Map firstPart = (Map) resParts.get(0);
return firstPart.get("text").toString();
}
return "AI 回傳格式異常";
} catch (Exception e) {
e.printStackTrace();
return "AI 服務呼叫失敗: " + e.getMessage();
}
}
}
React
前端要負責的事就比較簡單:
- 管理對話狀態( useState)
- 接收使用者輸入並送出至後端 ( fetch )
- 接收 AI 回應並更新對話紀錄
- 控制送出行為( useEffect )
- 處理非同步流程與錯誤狀態 (async / await )
- 維持對話可用性(自動捲動 useEffect)
import React, { useState, useRef, useEffect } from 'react';
import { FaAngleRight, FaPaperPlane } from 'react-icons/fa';
interface Message {
role: 'user' | 'ai';
text: string;
}
interface Props {
onClose: () => void;
medicalData: any;
}
export default function RightPanelContent({
onClose,
medicalData
}: Props) {
const [messages, setMessages] = useState<Message[]>([
{
role: 'ai',
text: '你好!我是您的醫療 AI 助手,已準備好分析病歷資料。請問有什麼我可以幫您的?'
}
]);
const [inputValue, setInputValue] = useState('');
const [loading, setLoading] = useState(false);
const scrollRef = useRef<HTMLDivElement>(null);
// 自動捲動到最底部
useEffect(() => {
if (scrollRef.current) {
scrollRef.current.scrollTop = scrollRef.current.scrollHeight;
}
}, [messages, loading]);
const handleSendMessage = async () => {
if (!inputValue.trim() || loading) return;
const userMessage = inputValue;
setMessages(prev => [...prev, { role: 'user', text: userMessage }]);
setInputValue('');
setLoading(true);
try { // inlineFetch的寫法
const response = await fetch('http://localhost:8080/api/ai/chat', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
message: userMessage,
context: JSON.stringify(medicalData)
})
});
const result = await response.json();
setMessages(prev => [...prev, { role: 'ai', text: result.reply }]);
} catch (error) {
setMessages(prev => [
...prev,
{ role: 'ai', text: '連線失敗,請檢查後端服務。' }
]);
} finally {
setLoading(false);
}
};
return (
<div>
{/* Header */}
<div>
<span>AI 智能對話助手</span>
<button onClick={onClose}>
<FaAngleRight />
</button>
</div>
{/* 對話區 */}
<div ref={scrollRef}>
{messages.map((msg, index) => (
<div key={index}>
<strong>{msg.role === 'user' ? 'User' : 'AI'}:</strong>
<span>{msg.text}</span>
</div>
))}
{loading && <div>AI 正在思考中...</div>}
</div>
{/* 輸入區 */}
<div>
<input
type="text"
placeholder="請輸入問題..."
value={inputValue}
onChange={e => setInputValue(e.target.value)}
onKeyDown={e => e.key === 'Enter' && handleSendMessage()}
/>
<button onClick={handleSendMessage}>
<FaPaperPlane />
</button>
</div>
</div>
);
}