castad-pre-v0.3/castad-data/components/ApiKeySelector.tsx

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;