89 lines
3.3 KiB
TypeScript
89 lines
3.3 KiB
TypeScript
|
|
import { GoogleGenAI, Type } from "@google/genai";
|
|
|
|
const apiKey = import.meta.env.VITE_GEMINI_API_KEY || process.env.GEMINI_API_KEY || process.env.API_KEY || '';
|
|
if (!apiKey) console.error("API Key is missing!");
|
|
const ai = new GoogleGenAI({ apiKey });
|
|
|
|
export interface DetailedSearchResult {
|
|
title: string;
|
|
uri: string;
|
|
lat?: number;
|
|
lng?: number;
|
|
}
|
|
|
|
export const geminiService = {
|
|
async searchNearbyWiFi(query: string, location: { lat: number, lng: number }) {
|
|
try {
|
|
console.log("Stage 1: 정보 검색 시작...");
|
|
|
|
// Stage 1: Google Search/Maps Grounding을 통해 원시 데이터 확보
|
|
const searchResponse = await ai.models.generateContent({
|
|
model: "gemini-2.5-flash",
|
|
contents: `내 위치(위도: ${location.lat}, 경도: ${location.lng}) 주변에서 "${query}" 장소들을 찾아주세요.
|
|
각 장소의 이름, 주소, 그리고 가능한 경우 위도와 경도 정보를 상세히 포함해서 답변해 주세요.`,
|
|
config: {
|
|
tools: [{ googleSearch: {} }, { googleMaps: {} }],
|
|
toolConfig: {
|
|
retrievalConfig: {
|
|
latLng: {
|
|
latitude: location.lat,
|
|
longitude: location.lng
|
|
}
|
|
}
|
|
}
|
|
},
|
|
});
|
|
|
|
const rawText = searchResponse.text || "";
|
|
console.log("Stage 1 원문 결과:", rawText);
|
|
|
|
if (!rawText) {
|
|
return { text: "검색 결과를 가져오지 못했습니다.", results: [] };
|
|
}
|
|
|
|
console.log("Stage 2: 데이터 구조화 추출 시작...");
|
|
|
|
// Stage 2: 획득한 텍스트에서 JSON 형태로 좌표 및 정보 정밀 추출
|
|
const parseResponse = await ai.models.generateContent({
|
|
model: "gemini-3-flash-preview",
|
|
contents: `다음은 주변 장소 검색 결과 텍스트입니다. 이 텍스트에서 언급된 모든 장소를 추출하여 JSON 배열로 응답해 주세요.
|
|
각 객체는 title, uri(구글맵 링크), lat(숫자), lng(숫자) 속성을 가져야 합니다.
|
|
좌표가 텍스트에 직접 없더라도 문맥상 추론 가능하거나 본문에 포함되어 있다면 반드시 숫자로 변환하세요.
|
|
|
|
텍스트:
|
|
${rawText}`,
|
|
config: {
|
|
responseMimeType: "application/json",
|
|
responseSchema: {
|
|
type: Type.ARRAY,
|
|
items: {
|
|
type: Type.OBJECT,
|
|
properties: {
|
|
title: { type: Type.STRING, description: "장소 이름" },
|
|
uri: { type: Type.STRING, description: "구글 맵 링크" },
|
|
lat: { type: Type.NUMBER, description: "위도 (Latitude)" },
|
|
lng: { type: Type.NUMBER, description: "경도 (Longitude)" },
|
|
},
|
|
required: ["title", "lat", "lng"]
|
|
}
|
|
}
|
|
}
|
|
});
|
|
|
|
let results: DetailedSearchResult[] = [];
|
|
try {
|
|
results = JSON.parse(parseResponse.text || "[]");
|
|
console.log("Stage 2 추출 성공:", results);
|
|
} catch (e) {
|
|
console.error("JSON 파싱 실패:", e);
|
|
}
|
|
|
|
return { text: rawText, results };
|
|
} catch (error: any) {
|
|
console.error("Gemini 검색 에러:", error);
|
|
return { text: `에러: ${error.message}`, results: [] };
|
|
}
|
|
}
|
|
};
|