150 lines
6.6 KiB
TypeScript
150 lines
6.6 KiB
TypeScript
import React, { useState, useEffect } from 'react';
|
|
import { Key } from 'lucide-react';
|
|
|
|
/**
|
|
* ApiKeySelector 컴포넌트의 props 정의
|
|
* @interface ApiKeySelectorProps
|
|
* @property {(key?: string) => void} onKeySelected - API 키가 성공적으로 선택되거나 입력되었을 때 호출될 콜백 함수. (선택된 키를 인자로 받을 수 있음)
|
|
* @property {string | null} [initialError] - 초기 에러 메시지 (선택 사항)
|
|
*/
|
|
interface ApiKeySelectorProps {
|
|
onKeySelected: (key?: string) => void;
|
|
initialError?: string | null;
|
|
}
|
|
|
|
/**
|
|
* API 키 선택 및 입력 컴포넌트
|
|
* Gemini 및 Veo 모델 사용을 위해 Google Cloud Project의 API 키를 설정하도록 안내하고,
|
|
* AI Studio 환경 또는 수동 입력 방식을 제공합니다.
|
|
*/
|
|
const ApiKeySelector: React.FC<ApiKeySelectorProps> = ({ onKeySelected, initialError }) => {
|
|
const [loading, setLoading] = useState(false); // 로딩 상태 관리
|
|
const [error, setError] = useState<string | null>(initialError || null); // 에러 메시지 관리
|
|
const [manualKey, setManualKey] = useState(''); // 수동으로 입력된 API 키
|
|
const [isAiStudio, setIsAiStudio] = useState(false); // 현재 환경이 Google AI Studio인지 여부
|
|
|
|
/**
|
|
* 환경 체크 및 초기 API 키 확인
|
|
* 컴포넌트 마운트 시 Google AI Studio 환경인지 확인하고, 이미 API 키가 선택되어 있는지 검사합니다.
|
|
*/
|
|
const checkKey = async () => {
|
|
try {
|
|
// window 객체에 aistudio 속성이 있는지 확인하여 AI Studio 환경을 감지합니다.
|
|
if ((window as any).aistudio) {
|
|
setIsAiStudio(true);
|
|
// AI Studio에서 이미 API 키가 선택되어 있는지 확인합니다.
|
|
const hasKey = await (window as any).aistudio.hasSelectedApiKey();
|
|
if (hasKey && !initialError) {
|
|
// 키가 이미 선택되어 있고 초기 에러가 없으면 onKeySelected 콜백을 호출하여 앱 시작
|
|
onKeySelected();
|
|
}
|
|
}
|
|
} catch (e) {
|
|
console.error("API 키 확인 중 오류 발생:", e);
|
|
// 오류가 발생해도 isAiStudio 상태는 유지
|
|
}
|
|
};
|
|
|
|
// 컴포넌트가 처음 렌더링될 때 한 번만 checkKey 함수 실행
|
|
useEffect(() => {
|
|
checkKey();
|
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
}, []);
|
|
|
|
/**
|
|
* AI Studio에서 API 키 선택 핸들러
|
|
* AI Studio의 `openSelectKey()` 함수를 호출하여 API 키 선택 UI를 띄웁니다.
|
|
*/
|
|
const handleSelectKey = async () => {
|
|
setLoading(true); // 로딩 상태 시작
|
|
setError(null); // 기존 에러 메시지 초기화
|
|
try {
|
|
if((window as any).aistudio) {
|
|
await (window as any).aistudio.openSelectKey(); // AI Studio 키 선택 대화 상자 열기
|
|
onKeySelected(); // 키 선택 성공 시 콜백 호출
|
|
}
|
|
} catch (e: any) {
|
|
console.error("API 키 선택 중 오류 발생:", e);
|
|
// 특정 에러 메시지에 따라 사용자에게 더 명확한 안내 제공
|
|
if (e.message && e.message.includes("Requested entity was not found")) {
|
|
setError("유효하지 않은 프로젝트/키가 선택되었습니다. 다시 시도해주세요.");
|
|
} else {
|
|
setError("API 키 선택에 실패했습니다. 다시 시도해주세요.");
|
|
}
|
|
} finally {
|
|
setLoading(false); // 로딩 상태 종료
|
|
}
|
|
};
|
|
|
|
/**
|
|
* 수동 API 키 입력 폼 제출 핸들러
|
|
* 사용자가 직접 입력한 API 키를 검증하고 콜백 함수를 호출합니다.
|
|
*/
|
|
const handleManualSubmit = (e: React.FormEvent) => {
|
|
e.preventDefault(); // 폼 기본 제출 동작 방지
|
|
if(!manualKey.trim()) {
|
|
setError("유효한 API 키를 입력해주세요."); // 빈 값일 경우 에러 메시지
|
|
return;
|
|
}
|
|
onKeySelected(manualKey.trim()); // 유효한 키가 입력되면 콜백 호출
|
|
};
|
|
|
|
return (
|
|
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black bg-opacity-90 p-4">
|
|
<div className="glass-panel p-8 rounded-2xl max-w-md w-full text-center border border-purple-500/30 shadow-2xl shadow-purple-900/20">
|
|
<div className="mb-6 flex justify-center">
|
|
<div className="p-4 bg-purple-900/30 rounded-full">
|
|
<Key className="w-8 h-8 text-purple-400" />
|
|
</div>
|
|
</div>
|
|
|
|
<h2 className="text-2xl font-bold text-white mb-2">API 키 필요</h2>
|
|
<p className="text-gray-400 mb-6">
|
|
Veo 및 Gemini로 고품질 뮤직 비디오를 생성하려면, 결제 가능한 Google Cloud 프로젝트의 API 키를 선택해주세요.
|
|
</p>
|
|
|
|
{error && (
|
|
<div className="mb-4 p-3 bg-red-900/30 border border-red-500/30 rounded text-red-200 text-sm">
|
|
{error}
|
|
</div>
|
|
)}
|
|
|
|
{isAiStudio ? (
|
|
// AI Studio 환경일 경우 API 키 선택 버튼 제공
|
|
<button
|
|
onClick={handleSelectKey}
|
|
disabled={loading}
|
|
className="w-full py-3 px-4 bg-gradient-to-r from-purple-600 to-pink-600 hover:from-purple-500 hover:to-pink-500 text-white font-bold rounded-xl transition-all transform hover:scale-105 disabled:opacity-50 disabled:cursor-not-allowed shadow-lg"
|
|
>
|
|
{loading ? '연결 중...' : 'API 키 선택'}
|
|
</button>
|
|
) : (
|
|
// 일반 브라우저 환경일 경우 수동 API 키 입력 폼 제공
|
|
<form onSubmit={handleManualSubmit} className="flex flex-col gap-4">
|
|
<input
|
|
type="password" // 비밀번호 타입으로 입력 값 숨김
|
|
value={manualKey}
|
|
onChange={(e) => setManualKey(e.target.value)}
|
|
placeholder="Gemini API 키 입력"
|
|
className="w-full p-3 rounded-xl bg-white/10 border border-white/20 text-white placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-purple-500"
|
|
/>
|
|
<button
|
|
type="submit"
|
|
className="w-full py-3 px-4 bg-gradient-to-r from-purple-600 to-pink-600 hover:from-purple-500 hover:to-pink-500 text-white font-bold rounded-xl transition-all transform hover:scale-105 shadow-lg"
|
|
>
|
|
앱 시작
|
|
</button>
|
|
</form>
|
|
)}
|
|
|
|
<div className="mt-4 text-xs text-gray-500">
|
|
<a href="https://ai.google.dev/gemini-api/docs/billing" target="_blank" rel="noreferrer" className="underline hover:text-gray-300">
|
|
결제 문서 보기
|
|
</a>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default ApiKeySelector; |